Added DelegationListAdapter, small refactor of delegates

This commit is contained in:
Denis Karmyshakov 2018-03-16 17:54:36 +03:00
parent e3e978b7f7
commit 69f1c9af4a
7 changed files with 278 additions and 172 deletions

View File

@ -24,6 +24,8 @@ import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import java.util.List;
/**
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
* Default {@link #getItemViewType} is generating on construction of object.
@ -43,6 +45,28 @@ public abstract class AdapterDelegate<TViewHolder extends RecyclerView.ViewHolde
return defaultItemViewType;
}
/**
* Returns if object is processable by this delegate.
*
* @param items Items to check;
* @param adapterPosition Position of item in adapter;
* @param collectionPosition Position of item in collection;
* @return True if item is processable by this delegate.
*/
public abstract boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition);
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param items Items in adapter;
* @param adapterPosition Position of item in adapter;
* @param collectionPosition Position of item in collection;
* @return Unique item ID.
*/
public long getItemId(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
return 0;
}
/**
* Creates ViewHolder to bind item to it later.
*
@ -52,4 +76,21 @@ public abstract class AdapterDelegate<TViewHolder extends RecyclerView.ViewHolde
@NonNull
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
/**
* Binds item to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param items Items in adapter;
* @param adapterPosition Position of item in adapter;
* @param collectionPosition Position of item in collection that contains item;
* @param payloads Payloads;
*/
public abstract void onBindViewHolder(
@NonNull final RecyclerView.ViewHolder holder,
@NonNull final List<Object> items,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> payloads
);
}

View File

@ -0,0 +1,60 @@
package ru.touchin.roboswag.components.adapters
import android.support.v7.widget.RecyclerView
import android.util.SparseArray
import android.view.ViewGroup
/**
* Manager for delegation callbacks from [RecyclerView.Adapter] to delegates.
*/
class DelegatesManager {
private val delegates = SparseArray<AdapterDelegate<*>>()
fun getItemViewType(items: List<*>, adapterPosition: Int, collectionPosition: Int): Int {
for (index in 0 until delegates.size()) {
val delegate = delegates.valueAt(index)
if (delegate.isForViewType(items, adapterPosition, collectionPosition)) {
return delegate.itemViewType
}
}
throw IllegalStateException("Delegate not found")
}
fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long {
val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition))
return delegate.getItemId(items, adapterPosition, collectionPosition)
}
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = getDelegate(viewType).onCreateViewHolder(parent)
fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<*>, adapterPosition: Int, collectionPosition: Int, payloads: List<Any>) {
val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition))
delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads)
}
/**
* Adds [ItemAdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegates.put(delegate.itemViewType, delegate)
/**
* Adds [PositionAdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate)
/**
* Removes [AdapterDelegate] from adapter.
*
* @param delegate Delegate to remove.
*/
fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType)
private fun getDelegate(viewType: Int): AdapterDelegate<*> =
delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType")
}

View File

@ -0,0 +1,82 @@
package ru.touchin.roboswag.components.adapters
import android.support.v7.recyclerview.extensions.AsyncDifferConfig
import android.support.v7.recyclerview.extensions.AsyncListDiffer
import android.support.v7.util.DiffUtil
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
/**
* Base adapter with delegation and diff computing on background thread.
*/
open class DelegationListAdapter<TItem>(diffCallback: DiffUtil.ItemCallback<TItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null
private val delegatesManager = DelegatesManager()
private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), AsyncDifferConfig.Builder<TItem>(diffCallback).build())
open fun getHeadersCount(): Int = 0
open fun getFootersCount(): Int = 0
override fun getItemCount(): Int = getHeadersCount() + differ.currentList.size + getFootersCount()
override fun getItemViewType(position: Int): Int = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position))
override fun getItemId(position: Int): Long = delegatesManager.getItemId(getList(), position, getCollectionPosition(position))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = delegatesManager.onCreateViewHolder(parent, viewType)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
val collectionPosition = getCollectionPosition(position)
if (itemClickListener != null && collectionPosition in 0 until getList().size) {
holder.itemView.setOnClickListener { itemClickListener?.invoke(getList()[collectionPosition], holder) }
} else {
holder.itemView.setOnClickListener(null)
}
delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads)
}
final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit
/**
* Adds [ItemAdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: ItemAdapterDelegate<*, *>) = delegatesManager.addDelegate(delegate)
/**
* Adds [PositionAdapterDelegate] to adapter.
*
* @param delegate Delegate to add.
*/
fun addDelegate(delegate: PositionAdapterDelegate<*>) = delegatesManager.addDelegate(delegate)
/**
* Submits a new list to be diffed, and displayed.
*
* If a list is already being displayed, a diff will be computed on a background thread, which
* will dispatch Adapter.notifyItem events on the main thread.
*
* @param list The new list to be displayed.
*/
fun submitList(list: List<TItem>) = differ.submitList(list)
/**
* Get the current List - any diffing to present this list has already been computed and
* dispatched via the ListUpdateCallback.
* <p>
* If a <code>null</code> List, or no List has been submitted, an empty list will be returned.
* <p>
* The returned list may not be mutated - mutations to content must be done through
* {@link #submitList(List)}.
*
* @return current List.
*/
fun getList(): List<TItem> = differ.currentList
fun getCollectionPosition(adapterPosition: Int): Int = adapterPosition - getHeadersCount()
}

View File

@ -2,7 +2,6 @@ package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import java.util.List;
@ -14,72 +13,73 @@ import java.util.List;
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate;
* @param <TItem> Type of items to bind to {@link RecyclerView.ViewHolder}s.
*/
@SuppressWarnings("unchecked")
public abstract class ItemAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder, TItem> extends AdapterDelegate<TViewHolder> {
@Override
public boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
return collectionPosition >= 0
&& collectionPosition < items.size()
&& isForViewType(items.get(collectionPosition), adapterPosition, collectionPosition);
}
/**
* Returns if object is processable by this delegate.
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int)}.
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int, List)}.
*
* @param item Item to check;
* @param positionInAdapter Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
* @param item Item to check;
* @param adapterPosition Position of item in adapter;
* @param collectionPosition Position of item in collection that contains item;
* @return True if item is processable by this delegate.
*/
public abstract boolean isForViewType(@NonNull final Object item, final int positionInAdapter, final int itemCollectionPosition);
public boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int collectionPosition) {
return true;
}
@Override
public long getItemId(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition);
}
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param item Item to check;
* @param positionInAdapter Position of item in adapter;
* @param positionInCollection Position of item in collection that contains item;
* @param item Item in adapter;
* @param adapterPosition Position of item in adapter;
* @param collectionPosition Position of item in collection that contains item;
* @return Unique item ID.
*/
public long getItemId(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) {
public long getItemId(@NonNull final TItem item, final int adapterPosition, final int collectionPosition) {
return 0;
}
/**
* Creates ViewHolder to bind item to it later.
*
* @param parent Container of ViewHolder's view.
* @return New ViewHolder.
*/
@NonNull
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
/**
* Binds item to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param item Item to check;
* @param positionInAdapter Position of item in adapter;
* @param positionInCollection Position of item in collection that contains item;
*/
public abstract void onBindViewHolder(
@NonNull final TViewHolder holder,
@NonNull final TItem item,
final int positionInAdapter,
final int positionInCollection
);
@Override
public void onBindViewHolder(
@NonNull final RecyclerView.ViewHolder holder,
@NonNull final List<Object> items,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> payloads
) {
//noinspection unchecked
onBindViewHolder((TViewHolder) holder, (TItem) items.get(collectionPosition), adapterPosition, collectionPosition, payloads);
}
/**
* Binds item with payloads to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param item Item to check;
* @param item Item in adapter;
* @param adapterPosition Position of item in adapter;
* @param collectionPosition Position of item in collection that contains item;
* @param payloads Payloads;
* @param positionInAdapter Position of item in adapter;
* @param positionInCollection Position of item in collection that contains item;
*/
public void onBindViewHolder(
public abstract void onBindViewHolder(
@NonNull final TViewHolder holder,
@NonNull final TItem item,
@NonNull final List<Object> payloads,
final int positionInAdapter,
final int positionInCollection
) {
//do nothing by default
}
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> payloads
);
}

View File

@ -0,0 +1,24 @@
package ru.touchin.roboswag.components.adapters
import android.support.v7.util.ListUpdateCallback
import android.support.v7.widget.RecyclerView
class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) {
adapter.notifyItemInserted(position + offsetProvider())
}
override fun onRemoved(position: Int, count: Int) {
adapter.notifyItemRangeRemoved(position + offsetProvider(), count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider())
}
override fun onChanged(position: Int, count: Int, payload: Any?) {
adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload)
}
}

View File

@ -2,7 +2,6 @@ package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import java.util.List;
@ -15,49 +14,54 @@ import java.util.List;
*/
public abstract class PositionAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> extends AdapterDelegate<TViewHolder> {
@Override
public boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
return isForViewType(adapterPosition);
}
/**
* Returns if object is processable by this delegate.
*
* @param positionInAdapter Position of item in adapter;
* @param adapterPosition Position of item in adapter;
* @return True if item is processable by this delegate.
*/
public abstract boolean isForViewType(final int positionInAdapter);
public abstract boolean isForViewType(final int adapterPosition);
@Override
public long getItemId(@NonNull final List<Object> objects, final int adapterPosition, final int itemsOffset) {
return getItemId(adapterPosition);
}
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param positionInAdapter Position of item in adapter;
* @param adapterPosition Position of item in adapter;
* @return Unique item ID.
*/
public long getItemId(final int positionInAdapter) {
public long getItemId(final int adapterPosition) {
return 0;
}
/**
* Creates ViewHolder to bind position to it later.
*
* @param parent Container of ViewHolder's view.
* @return New ViewHolder.
*/
@NonNull
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
/**
* Binds position to ViewHolder.
*
* @param holder ViewHolder to bind position to;
* @param positionInAdapter Position of item in adapter.
*/
public abstract void onBindViewHolder(@NonNull final TViewHolder holder, final int positionInAdapter);
@Override
public void onBindViewHolder(
@NonNull final RecyclerView.ViewHolder holder,
@NonNull final List<Object> items,
final int adapterPosition,
final int collectionPosition,
@NonNull final List<Object> payloads
) {
//noinspection unchecked
onBindViewHolder((TViewHolder) holder, adapterPosition, payloads);
}
/**
* Binds position with payloads to ViewHolder.
*
* @param holder ViewHolder to bind position to;
* @param payloads Payloads;
* @param positionInAdapter Position of item in adapter.
* @param adapterPosition Position of item in adapter;
* @param payloads Payloads.
*/
public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final List<Object> payloads, final int positionInAdapter) {
public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List<Object> payloads) {
//do nothing by default
}

View File

@ -1,105 +0,0 @@
/*
* Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov)
*
* This file is part of RoboSwag library.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ru.touchin.roboswag.components.utils;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.annotation.StyleableRes;
import android.util.AttributeSet;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ru.touchin.roboswag.core.log.Lc;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
/**
* Created by Gavriil Sitnikov on 18/07/2014.
* Manager for typefaces stored in 'assets/fonts' folder.
*/
public final class Typefaces {
private static final Map<String, Typeface> TYPEFACES_MAP = new HashMap<>();
/**
* Returns {@link Typeface} by name from assets 'fonts' folder.
*
* @param context Context of assets where typeface file stored in;
* @param name Full name of typeface (without extension, e.g. 'Roboto-Regular');
* @return {@link Typeface} from assets.
*/
@NonNull
public static Typeface getByName(@NonNull final Context context, @NonNull final String name) {
synchronized (TYPEFACES_MAP) {
Typeface result = TYPEFACES_MAP.get(name);
if (result == null) {
final AssetManager assetManager = context.getAssets();
result = Typeface.DEFAULT;
try {
final List<String> fonts = Arrays.asList(assetManager.list("fonts"));
if (fonts.contains(name + ".ttf")) {
result = Typeface.createFromAsset(assetManager, "fonts/" + name + ".ttf");
} else if (fonts.contains(name + ".otf")) {
result = Typeface.createFromAsset(assetManager, "fonts/" + name + ".otf");
} else {
Lc.assertion("Can't find .otf or .ttf file in assets folder 'fonts' with name: " + name);
}
} catch (final IOException exception) {
Lc.assertion(new ShouldNotHappenException("Can't get font " + name + '.'
+ "Did you forget to create assets folder named 'fonts'?", exception));
}
TYPEFACES_MAP.put(name, result);
}
return result;
}
}
/**
* Returns {@link Typeface} by name from assets 'fonts' folder.
*
* @param context Context of assets where typeface file stored in;
* @param attrs Attributes of view to get font from;
* @param styleableId Id of attribute set;
* @param attributeId Id of attribute;
* @return {@link Typeface} from assets.
*/
@NonNull
public static Typeface getFromAttributes(@NonNull final Context context, @NonNull final AttributeSet attrs,
@NonNull @StyleableRes final int[] styleableId, @StyleableRes final int attributeId) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, styleableId);
final String customTypeface = typedArray.getString(attributeId);
typedArray.recycle();
if (customTypeface != null) {
return getByName(context, customTypeface);
}
Lc.w("Couldn't find custom typeface. Returns default");
return Typeface.DEFAULT;
}
private Typefaces() {
}
}