RecyclerView用の非常に便利な抽象日曜大工アダプター

かつて、Android開発者としてのキャリアの夜明けに、既存のアプリケーションの例を見て、春の太陽のように美しいU2020で、非常に便利なアダプターの例を見つけました。 彼の名前はBindableAdapterです。 長い間、それをListViewとGridViewのアダプターの基礎として使用していましたが、それらの時代は終わりに近づき、RecyclerViewの時代が始まります。 この時代には新しい便利なアダプターが必要で、私はそれを作ろうとしました。



まず、Googleに「私の最愛の人はどこですか?」 「RecyclerBindableAdapter」と尋ねることにしましたが、彼は答えをくれませんでした。 私は正直なところ、私が快適に作業できるアダプターを見つけようとしましたが、それはすべて無駄でした。 私が見つけたすべての例は、特定の機能のみを実装し、抽象的であろうとしませんでした。 そして、率直に言って、それらの多くの瞬間は私を混乱させました。例えば、それらの多くでは、ContextとLayoutManagerが何らかの理由でコンストラクターに転送されました。 RecyclerView.Adapterには優れたonAttachedToRecyclerView(RecyclerView recyclerView)メソッドがありますが、結果のrecyclerViewからContextとLayoutManagerの両方を取得できます。



これらすべてにより、私はそれらが思慮深くよりも急いで書かれたと考えるようになりました。 私はどこでもRecyclerViewを使用し、毎回似たようなコードを大量に生成するのは、どういうわけかうまくいかないので、状況を修正して便利な抽象アダプターを書くことにしました。



そして、実際に何が必要ですか?



RecyclerViewのすべての新機能を最大限に活用しようとしながら、RecyclerViewに同じBindableAdapterを移植します。 いつものように、1つの「しかし」があります。RecyclerViewにはデフォルトでヘッダーとフッターがありません。 したがって、将来のユーザーが新しいタイプの要素を作成してこの問題を修正しようとすると、このため、位置番号はヘッダーの数だけシフトします。 これは悪いです。 そのため、事前にヘッダーとフッターを操作できるようにする必要があります。また、データを操作する方法でヘッダーとフッターを使用できるようにする必要があります。



それから、私がよく使う他のRecyclerView機能、すなわち要素フィルタリングとヘッダー視差を実装するのがいいと思いました。 ここでも、すべてがそれほどスムーズではありません。



フィルタリングを提供するアダプターは、オブジェクトの2つのリスト(一般リストと現在表示されているそれらのアイテムのリスト)を保存することを強制されます。 したがって、より多くのメモリを消費し、要素を操作するメソッドの動作は遅くなります。 メモリに追加のデータを事前に入力する必要はなく、フィルタリングはそれほど必要ありません。 そのため、メインアダプタを拡張する別のクラスにすることにしました。 したがって、フィルタリングが必要な場合にのみこのアダプターを使用し、その他の場合はメインアダプターを使用します。



ヘッダーの視差でも同様の状況が発生しています。 メインアダプタには、多くのヘッダーとフッターを含めることができます。 同時に複数の要素の視差を実装することは問題になる可能性が高く、見苦しいため、試しても意味がありません。 同様に別のクラスにします。



この時点で、SimpleAdapterの類似物があればいいと思いました。 レイアウトIDとViewHolderを挿入するだけで、1行として宣言できます。 結局のところ、単純なリストのために大量のコードを作成するのはなぜでしょう。



その結果、タスクは4つのアダプターを作成することでした。

  1. RecyclerBindableAdapter-メインアダプター
  2. FilterBindableAdapter-フィルター可能なアダプター
  3. ParallaxBindableAdapter-ヘッダー視差機能を備えたアダプター
  4. SimpleBindableAdapter-シンプルなアダプター


降りる



RecyclerBindableAdapter



まず、ヘッダーとフッターを追加できるようにします。これは、かつてStackOverflowの広大さで出会い、アダプターのニーズに応じて調整した類似の実装です。 Androidを使用しているほとんどの人が同様の実装を見たり、行ったりしていると思うので、詳しくは説明しません。



ヘッダーとフッターを追加できるようにする
//   Header'  Footer' public static final int TYPE_HEADER = 7898; public static final int TYPE_FOOTER = 7899; //     private List<View> headers = new ArrayList<>(); private List<View> footers = new ArrayList<>(); @Override public VH onCreateViewHolder(ViewGroup viewGroup, int type) { //   ,    ViewHolder if (type != TYPE_HEADER && type != TYPE_FOOTER) { return (VH) onCreteItemViewHolder(viewGroup, type); //  header  footer } else { //  FrameLayout   ,   ,      View FrameLayout frameLayout = new FrameLayout(viewGroup.getContext()); frameLayout.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); //  ViewHolder  header'  footer'.    ,         FrameLayout return (VH) new HeaderFooterViewHolder(frameLayout); } } @Override final public void onBindViewHolder(final RecyclerView.ViewHolder vh, int position) { //                  header/footer,      if (isHeader(position)) { View v = headers.get(position); prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else if (isFooter(position)) { View v = footers.get(position - getRealItemCount() - getHeadersCount()); prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else { onBindItemViewHolder((VH) vh, position - headers.size(), getItemType(position)); } } @Override final public int getItemViewType(int position) { //      if (isHeader(position)) { return TYPE_HEADER; } else if (isFooter(position)) { return TYPE_FOOTER; } int type = getItemType(position); if (type == TYPE_HEADER || type == TYPE_FOOTER) { throw new IllegalArgumentException("Item type cannot equal " + TYPE_HEADER + " or " + TYPE_FOOTER); } return type; }
      
      







次に、データを操作するためのメソッドを作成する必要があります。それらの名前は、ListView / GridViewのBindableAdapterから取得します。



データを操作する
 //    private List<T> items = new ArrayList<>(); //  -      header'  footer' public int getRealItemCount() { return items.size(); } //     public T getItem(int position) { return items.get(position); } //     public void add(int position, T item) { //    items.add(position, item); //   notifyItemInserted(position); //     ViewHolder    ,         int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } //     public void add(T item) { //    items.add(item); //   notifyItemInserted(items.size() - 1 + getHeadersCount()); } // List    ,       List,      .    ,           ,     public void addAll(List<? extends T> items) { final int size = this.items.size(); //    this.items.addAll(items); //   notifyItemRangeInserted(size + getHeadersCount(), items.size()); } //     public void set(int position, T item) { //    items.set(position, item); //     ViewHolder    ,         int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; //  notifyItemRangeChanged(positionStart, itemCount); } //     public void removeChild(int position) { //    items.remove(position); //  notifyItemRemoved(position + getHeadersCount()); //     ViewHolder    ,         int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } //      Header'  Footer' public void clear() { final int size = items.size(); //  items.clear(); //   notifyItemRangeRemoved(getHeadersCount(), size); } //       public void moveChildTo(int fromPosition, int toPosition) { //         if (toPosition != -1 && toPosition < items.size()) { //    ... final T item = items.remove(fromPosition); //    items.add(toPosition, item); //  notifyItemMoved(getHeadersCount() + fromPosition, getHeadersCount() + toPosition); //     ViewHolder    ,         int positionStart = fromPosition < toPosition ? fromPosition : toPosition; int itemCount = Math.abs(fromPosition - toPosition) + 1; notifyItemRangeChanged(positionStart + getHeadersCount(), itemCount); } } //   public int indexOf(T object) { return items.indexOf(object); }
      
      







基本的に、RecyclerBindableAdapterは完成しました。 全文は、 ここまたはスポイラーの下で見ることができます。



RecyclerBindableAdapter
 public abstract class RecyclerBindableAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { public static final int TYPE_HEADER = 7898; public static final int TYPE_FOOTER = 7899; private List<View> headers = new ArrayList<>(); private List<View> footers = new ArrayList<>(); private List<T> items = new ArrayList<>(); private RecyclerView.LayoutManager manager; private LayoutInflater inflater; private GridLayoutManager.SpanSizeLookup spanSizeLookup = new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return getGridSpan(position); } }; public int getRealItemCount() { return items.size(); } public T getItem(int position) { return items.get(position); } public void add(int position, T item) { items.add(position, item); notifyItemInserted(position); int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } public void add(T item) { items.add(item); notifyItemInserted(items.size() - 1 + getHeadersCount()); } public void addAll(List<? extends T> items) { final int size = this.items.size(); this.items.addAll(items); notifyItemRangeInserted(size + getHeadersCount(), items.size()); } public void set(int position, T item) { items.set(position, item); int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } public void removeChild(int position) { items.remove(position); notifyItemRemoved(position + getHeadersCount()); int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } public void clear() { final int size = items.size(); items.clear(); notifyItemRangeRemoved(getHeadersCount(), size); } public void moveChildTo(int fromPosition, int toPosition) { if (toPosition != -1 && toPosition < items.size()) { final T item = items.remove(fromPosition); items.add(toPosition, item); notifyItemMoved(getHeadersCount() + fromPosition, getHeadersCount() + toPosition); int positionStart = fromPosition < toPosition ? fromPosition : toPosition; int itemCount = Math.abs(fromPosition - toPosition) + 1; notifyItemRangeChanged(positionStart + getHeadersCount(), itemCount); } } //@TODO need test public int indexOf(T object) { return items.indexOf(object); } @Override public VH onCreateViewHolder(ViewGroup viewGroup, int type) { //if our position is one of our items (this comes from getItemViewType(int position) below) if (type != TYPE_HEADER && type != TYPE_FOOTER) { return (VH) onCreteItemViewHolder(viewGroup, type); //else we have a header/footer } else { //create a new framelayout, or inflate from a resource FrameLayout frameLayout = new FrameLayout(viewGroup.getContext()); //make sure it fills the space frameLayout.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return (VH) new HeaderFooterViewHolder(frameLayout); } } @Override final public void onBindViewHolder(final RecyclerView.ViewHolder vh, int position) { //check what type of view our position is if (isHeader(position)) { View v = headers.get(position); //add our view to a header view and display it prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else if (isFooter(position)) { View v = footers.get(position - getRealItemCount() - getHeadersCount()); //add our view to a footer view and display it prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else { //it's one of our items, display as required onBindItemViewHolder((VH) vh, position - headers.size(), getItemType(position)); } } private void prepareHeaderFooter(HeaderFooterViewHolder vh, View view) { //if it's a staggered grid, span the whole layout if (manager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); layoutParams.setFullSpan(true); vh.itemView.setLayoutParams(layoutParams); } //if the view already belongs to another layout, remove it if (view.getParent() != null) { ((ViewGroup) view.getParent()).removeView(view); } //empty out our FrameLayout and replace with our header/footer ((ViewGroup) vh.itemView).removeAllViews(); ((ViewGroup) vh.itemView).addView(view); } private boolean isHeader(int position) { return (position < headers.size()); } private boolean isFooter(int position) { return footers.size() > 0 && (position >= getHeadersCount() + getRealItemCount()); } protected VH onCreteItemViewHolder(ViewGroup parent, int type) { return viewHolder(inflater.inflate(layoutId(type), parent, false), type); } @Override public int getItemCount() { return headers.size() + getRealItemCount() + footers.size(); } @Override final public int getItemViewType(int position) { //check what type our position is, based on the assumption that the order is headers > items > footers if (isHeader(position)) { return TYPE_HEADER; } else if (isFooter(position)) { return TYPE_FOOTER; } int type = getItemType(position); if (type == TYPE_HEADER || type == TYPE_FOOTER) { throw new IllegalArgumentException("Item type cannot equal " + TYPE_HEADER + " or " + TYPE_FOOTER); } return type; } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); if (manager == null) { setManager(recyclerView.getLayoutManager()); } if (inflater == null) { this.inflater = LayoutInflater.from(recyclerView.getContext()); } } private void setManager(RecyclerView.LayoutManager manager) { this.manager = manager; if (this.manager instanceof GridLayoutManager) { ((GridLayoutManager) this.manager).setSpanSizeLookup(spanSizeLookup); } else if (this.manager instanceof StaggeredGridLayoutManager) { ((StaggeredGridLayoutManager) this.manager).setGapStrategy( StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); } } protected int getGridSpan(int position) { if (isHeader(position) || isFooter(position)) { return getMaxGridSpan(); } position -= headers.size(); if (getItem(position) instanceof SpanItemInterface) { return ((SpanItemInterface) getItem(position)).getGridSpan(); } return 1; } protected int getMaxGridSpan() { if (manager instanceof GridLayoutManager) { return ((GridLayoutManager) manager).getSpanCount(); } else if (manager instanceof StaggeredGridLayoutManager) { return ((StaggeredGridLayoutManager) manager).getSpanCount(); } return 1; } //add a header to the adapter public void addHeader(View header) { if (!headers.contains(header)) { headers.add(header); //animate notifyItemInserted(headers.size() - 1); } } //@TODO need test public void removeHeader(View header) { if (headers.contains(header)) { //animate notifyItemRemoved(headers.indexOf(header)); headers.remove(header); } } //add a footer to the adapter public void addFooter(View footer) { if (!footers.contains(footer)) { footers.add(footer); //animate notifyItemInserted(headers.size() + getItemCount() + footers.size() - 1); } } //@TODO need test public void removeFooter(View footer) { if (footers.contains(footer)) { //animate notifyItemRemoved(headers.size() + getItemCount() + footers.indexOf(footer)); footers.remove(footer); } } public int getHeadersCount() { return headers.size(); } protected View getHeader(int location) { return headers.get(location); } public int getFootersCount() { return footers.size(); } protected View getFooter(int location) { return footers.get(location); } protected int getItemType(int position) { return 0; } abstract protected void onBindItemViewHolder(VH viewHolder, int position, int type); protected abstract VH viewHolder(View view, int type); protected abstract @LayoutRes int layoutId(int type); public interface SpanItemInterface { int getGridSpan(); } //our header/footer RecyclerView.ViewHolder is just a FrameLayout public static class HeaderFooterViewHolder extends RecyclerView.ViewHolder { public HeaderFooterViewHolder(View itemView) { super(itemView); } } }
      
      







次に、いくつかの例を作成します。



 public class LinearExampleAdapter extends RecyclerBindableAdapter<Integer, LinearViewHolder> { private LinearViewHolder.ActionListener actionListener; // layout id    @Override protected int layoutId(int type) { return R.layout.linear_example_item; } // ViewHolder @Override protected LinearViewHolder viewHolder(View view, int type) { return new LinearViewHolder(view); } //    @Override protected void onBindItemViewHolder(LinearViewHolder viewHolder, final int position, int type) { viewHolder.bindView(getItem(position), position, actionListener); } //    public void setActionListener(LinearViewHolder.ActionListener actionListener) { this.actionListener = actionListener; } }
      
      





ViewHolder(注意バターナイフ7)
 public class LinearViewHolder extends RecyclerView.ViewHolder { @Bind(R.id.linear_example_item_text) TextView text; private int position; private ActionListener actionListener; public LinearViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } public void bindView(Integer item, int position, ActionListener listener) { actionListener = listener; this.position = position; text.setText(String.valueOf(item)); } @OnClick(R.id.linear_example_item_move_to_top) protected void OnMoveToTopClick() { if (actionListener != null) { actionListener.onMoveToTop(position); } } @OnClick(R.id.linear_example_item_remove) protected void OnRemoveClick() { if (actionListener != null) { actionListener.OnRemove(position); } } @OnClick(R.id.linear_example_item_up) protected void OnUpClick() { if (actionListener != null) { actionListener.OnUp(position); } } @OnClick(R.id.linear_example_item_down) protected void OnDownClick() { if (actionListener != null) { actionListener.OnDown(position); } } public interface ActionListener { void onMoveToTop(int position); void OnRemove(int position); void OnUp(int position); void OnDown(int position); } }
      
      







だから、今ではすべてがシンプルです。 アダプターの操作は、時々簡素化されています。 ここにこの例を見つけることができます、そして、ここにいくつかのタイプの例があります。



FilterBindableAdapter



フィルター可能なアダプターを作成しましょう。 RecyclerBindableAdapterを展開し、まずすべてのオブジェクトと現在表示されているオブジェクトの2つのリストを作成します。 メソッドの一部を再定義して、2つのリストで機能するようにしました。



2つのリストを作成します。 メソッドの再定義
 //  private List<T> originalValues; private List<T> objects; protected FilterBindableAdapter() { objects = new ArrayList<T>(); originalValues = new ArrayList<T>(); } //       ,            ,       @Override public void addAll(List<? extends T> data) { //             if (objects.containsAll(data)) { return; } //       objects.clear(); objects.addAll(data); //       originalValues.clear(); originalValues.addAll(data); // notifyItemRangeInserted(getHeadersCount(), data.size()); } //          
      
      







次に、フィルタリングを行いましょう。



フィルタリングを行う
 //     public Filter getFilter() { if (filter == null) { filter = new ArrayFilter(); } return filter; } //        public interface OnFilterObjectCallback { void handle(int countFilterObject); } //    private class ArrayFilter extends Filter { //   @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); // prefix   ,     if (prefix == null || prefix.length() == 0) { ArrayList<T> list; synchronized (lock) { list = new ArrayList<T>(originalValues); } results.values = list; results.count = list.size(); //     } else { //         String prefixString = prefix.toString().toLowerCase(); ArrayList<T> values; synchronized (lock) { values = new ArrayList<T>(originalValues); } final int count = values.size(); final ArrayList<T> newValues = new ArrayList<T>(); //       for (int i = 0; i < count; i++) { final T value = values.get(i); final String valueText = itemToString(value).toLowerCase(); //            if (valueText.startsWith(prefixString)) { newValues.add(value); //  ,          } else { final String[] words = valueText.split(" "); //   ,       for (String word : words) { if (word.startsWith(prefixString)) { newValues.add(value); break; } } } } results.values = newValues; results.count = newValues.size(); } return results; } //   @Override protected void publishResults(CharSequence constraint, FilterResults results) { objects = (List<T>) results.values; // callback    if (onFilterObjectCallback != null) { onFilterObjectCallback.handle(results.count); } notifyDataSetChanged(); } }
      
      







アダプターの例は、1つのメソッドを除いて、RecyclerBindableAdapterの例と完全に似ています。 このメソッドは、要素を文字列に変換して、要素をフィルタリングするためのものです。



 @Override protected String itemToString(String item) { return item; }
      
      





全文は、 ここまたはスポイラーの下で見ることができます。



FilterBindableAdapter
 public abstract class FilterBindableAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerBindableAdapter<T, VH> { private final Object lock = new Object(); private List<T> originalValues; private List<T> objects; private ArrayFilter filter; private OnFilterObjectCallback onFilterObjectCallback; protected FilterBindableAdapter() { objects = new ArrayList<T>(); originalValues = new ArrayList<T>(); } @Override public void addAll(List<? extends T> data) { if (objects.containsAll(data)) { return; } objects.clear(); objects.addAll(data); originalValues.clear(); originalValues.addAll(data); notifyItemRangeInserted(getHeadersCount(), data.size()); } //@TODO need test public void addShowed(List<? extends T> data) { objects.clear(); objects.addAll(data); notifyDataSetChanged(); } //@TODO need test @Override public void removeChild(int position) { objects.remove(position); originalValues.remove(position); objects.remove(position); notifyItemRemoved(position + getHeadersCount()); int positionStart = position + getHeadersCount(); int itemCount = objects.size() - position; notifyItemRangeChanged(positionStart, itemCount); } //@TODO need test public void setOnFilterObjectCallback(OnFilterObjectCallback objectCallback) { onFilterObjectCallback = objectCallback; } @Override public T getItem(int position) { return objects.get(position); } @Override public int getRealItemCount() { return objects.size(); } @Override public long getItemId(int position) { return position; } protected abstract String itemToString(T item); public Filter getFilter() { if (filter == null) { filter = new ArrayFilter(); } return filter; } public interface OnFilterObjectCallback { void handle(int countFilterObject); } private class ArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if (originalValues == null) { synchronized (lock) { originalValues = new ArrayList<T>(objects); } } if (prefix == null || prefix.length() == 0) { ArrayList<T> list; synchronized (lock) { list = new ArrayList<T>(originalValues); } results.values = list; results.count = list.size(); } else { String prefixString = prefix.toString().toLowerCase(); ArrayList<T> values; synchronized (lock) { values = new ArrayList<T>(originalValues); } final int count = values.size(); final ArrayList<T> newValues = new ArrayList<T>(); for (int i = 0; i < count; i++) { final T value = values.get(i); final String valueText = itemToString(value).toLowerCase(); // First match against the whole, non-splitted value if (valueText.startsWith(prefixString)) { newValues.add(value); } else { final String[] words = valueText.split(" "); // Start at index 0, in case valueText starts with space(s) for (String word : words) { if (word.startsWith(prefixString)) { newValues.add(value); break; } } } } results.values = newValues; results.count = newValues.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { //noinspection unchecked objects = (List<T>) results.values; if (onFilterObjectCallback != null) { onFilterObjectCallback.handle(results.count); } notifyDataSetChanged(); } } }
      
      







例はここにあります



ParallaxBindableAdapter



それでは、視差効果の作成を始めましょう。 最初は、ヘッダーのみの視差を作成する予定でしたが、フッターの視差を作成することは非常に興味深い経験になると考えました。 最初に、ヘッダーとフッターを追加するメソッドを再定義して、アダプターがそれぞれ1つのインスタンスのみを持つようにします。



addHeaderとaddFooterのオーバーライド
  @Override public void addHeader(View header) { if (getHeadersCount() == 0) { super.addHeader(header); } else { removeHeader(getHeader(0)); super.addHeader(header); } } @Override public void addFooter(View footer) { if (getFootersCount() == 0) { super.addFooter(footer); } else { removeFooter(getFooter(0)); super.addFooter(footer); } }
      
      







次に、ヘッダーが他の要素の下でクロールされないように、レンダリング領域を制限するコンテナを作成します。 すでに視差を行っている場合は、おそらく同様の実装にすでに遭遇している可能性があります。 この実装の違いは、フッターもサポートすることです。 onCreateViewHolder()でFrameLayoutの代わりにこのコンテナを使用します。

視差コンテナ
  public class ParallaxContainer extends FrameLayout { private final boolean isParallax; private final boolean isFooter; private int offset; public ParallaxContainer(Context context, boolean shouldClick, boolean isFooter) { super(context); isParallax = shouldClick; this.isFooter = isFooter; } @Override protected void dispatchDraw(@NonNull Canvas canvas) { if (isParallax) { int top = isFooter ? -offset : 0; int bottom = isFooter ? getBottom() : getBottom() + offset; Rect rect = new Rect(getLeft(), top, getRight(), bottom); canvas.clipRect(rect); } super.dispatchDraw(canvas); } public void setClipY(int offset) { this.offset = offset; invalidate(); } }
      
      







スクロール中にコンテナを移動するメソッドを作成しましょう。 これを行うには、onAttachedToRecyclerView()メソッドで、RecyclerViewでOnScrollListenerをハングさせます。 その中で、シフトのためのメソッドを呼び出します。 もちろん、視差効果を有効/無効にするためのメソッドを作成する必要があります。



シフトを実現
 //         setter'   private boolean isParallaxHeader = true; private boolean isParallaxFooter = true; public void setParallaxHeader(boolean isParallaxHeader) { this.isParallaxHeader = isParallaxHeader; } public void setParallaxFooter(boolean isParallaxFooter) { this.isParallaxFooter = isParallaxFooter; } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); // ScrollListener  RecyclerView recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // header             if (header != null && isParallaxHeader) { translateView(recyclerView.computeVerticalScrollOffset(), header, false); } // footer             if (footer != null && isParallaxFooter) { int range = recyclerView.computeVerticalScrollRange(); int extend = recyclerView.computeVerticalScrollExtent(); int offset = recyclerView.computeVerticalScrollOffset(); translateView(range - (extend + offset), footer, true); } } }); } //  private void translateView(float of, ParallaxContainer view, boolean isFooter) { float ofCalculated = of * SCROLL_MULTIPLIER; //  footer        ofCalculated = isFooter ? -ofCalculated : ofCalculated; //   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { view.setTranslationY(ofCalculated); } else { TranslateAnimation anim = new TranslateAnimation(0, 0, ofCalculated, ofCalculated); anim.setFillAfter(true); anim.setDuration(0); view.startAnimation(anim); } //   view.setClipY(Math.round(ofCalculated)); //  callback,     (  / ActionBar  ) if (parallaxScroll != null && !isFooter) { float left = Math.min(1, ((ofCalculated) / (view.getHeight() * SCROLL_MULTIPLIER))); parallaxScroll.onParallaxScroll(left, of, view); } }
      
      







全文は、 ここまたはスポイラーの下で見ることができます。



ParallaxBindableAdapter
 public abstract class ParallaxBindableAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerBindableAdapter<T, VH> { private static final float SCROLL_MULTIPLIER = 0.5f; private ParallaxContainer header; private ParallaxContainer footer; private OnParallaxScroll parallaxScroll; private boolean isParallaxHeader = true; private boolean isParallaxFooter = true; //parallax adapter may have only one header @Override public void addHeader(View header) { if (getHeadersCount() == 0) { super.addHeader(header); } else { removeHeader(getHeader(0)); super.addHeader(header); } } //parallax adapter may have only one header @Override public void addFooter(View footer) { if (getFootersCount() == 0) { super.addFooter(footer); } else { removeFooter(getFooter(0)); super.addFooter(footer); } } private void translateView(float of, ParallaxContainer view, boolean isFooter) { float ofCalculated = of * SCROLL_MULTIPLIER; ofCalculated = isFooter ? -ofCalculated : ofCalculated; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { view.setTranslationY(ofCalculated); } else { TranslateAnimation anim = new TranslateAnimation(0, 0, ofCalculated, ofCalculated); anim.setFillAfter(true); anim.setDuration(0); view.startAnimation(anim); } view.setClipY(Math.round(ofCalculated)); if (parallaxScroll != null && !isFooter) { float left = Math.min(1, ((ofCalculated) / (view.getHeight() * SCROLL_MULTIPLIER))); parallaxScroll.onParallaxScroll(left, of, view); } } @Override public VH onCreateViewHolder(ViewGroup viewGroup, int type) { //if our position is one of our items (this comes from getItemViewType(int position) below) if (type != TYPE_HEADER && type != TYPE_FOOTER) { return (VH) onCreteItemViewHolder(viewGroup, type); //else if we have a header } else if (type == TYPE_HEADER) { //create a new ParallaxContainer header = new ParallaxContainer(viewGroup.getContext(), isParallaxHeader, false); //make sure it fills the space header.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return (VH) new HeaderFooterViewHolder(header); //else we have a footer } else { //create a new ParallaxContainer footer = new ParallaxContainer(viewGroup.getContext(), isParallaxFooter, true); //make sure it fills the space footer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return (VH) new HeaderFooterViewHolder(footer); } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (header != null && isParallaxHeader) { translateView(recyclerView.computeVerticalScrollOffset(), header, false); } if (footer != null && isParallaxFooter) { int range = recyclerView.computeVerticalScrollRange(); int extend = recyclerView.computeVerticalScrollExtent(); int offset = recyclerView.computeVerticalScrollOffset(); translateView(range - (extend + offset), footer, true); } } }); } public void setParallaxHeader(boolean isParallaxHeader) { this.isParallaxHeader = isParallaxHeader; } public void setParallaxFooter(boolean isParallaxFooter) { this.isParallaxFooter = isParallaxFooter; } public void setOnParallaxScroll(OnParallaxScroll parallaxScroll) { this.parallaxScroll = parallaxScroll; this.parallaxScroll.onParallaxScroll(0, 0, header); } public interface OnParallaxScroll { /** * Event triggered when the parallax is being scrolled. * * @param percentage * @param offset * @param parallax */ void onParallaxScroll(float percentage, float offset, View parallax); } }
      
      







RecyclerBindableAdapter, . .



SimpleBindableAdapter



, , ViewHolder. , . .



非表示のテキスト
 public abstract class BindableViewHolder<T> extends RecyclerView.ViewHolder { public BindableViewHolder(View itemView) { super(itemView); } //      ,   OnItemClickListener public void bindView(final int position, final T item, final ActionListener actionListener) { if (actionListener != null) { itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { actionListener.OnItemClickListener(position, item); } }); } } //        public interface ActionListener { void OnItemClickListener(int position, Object Item); } }
      
      







. RecyclerBindableAdapter ViewHolder Java Reflection.



非表示のテキスト
 //  final,  ,   ,     . ,  VH    BindableViewHolder public final class SimpleBindableAdapter<T, VH extends BindableViewHolder> extends RecyclerBindableAdapter<T, VH> { //     layout id   @LayoutRes private final int layoutId; // ViewHolder    ,       ViewHolder Class<VH> vhClass; //     BindableViewHolder.ActionListener actionListener; public SimpleBindableAdapter(@LayoutRes int layoutId, Class<VH> vhClass) { this.layoutId = layoutId; this.vhClass = vhClass; } @Override protected void onBindItemViewHolder(BindableViewHolder viewHolder, int position, int type) { //   ViewHolder,       BindableViewHolder viewHolder.bindView(position, getItem(position), actionListener); } @Override protected VH viewHolder(View view, int type) { // Java Reflection    ViewHolder try { return (VH) vhClass.getConstructor(View.class).newInstance(view); } catch (InstantiationException e) { e.printStackTrace(); return null; } catch (IllegalAccessException e) { e.printStackTrace(); return null; } catch (NoSuchMethodException e) { e.printStackTrace(); return null; } catch (InvocationTargetException e) { e.printStackTrace(); return null; } } @Override protected int layoutId(int type) { return layoutId; } public void setActionListener(BindableViewHolder.ActionListener actionListener) { this.actionListener = actionListener; } }
      
      







, . .



 private SimpleBindableAdapter<Integer, SimpleViewHolder> simpleExampleAdapter; @Override protected void onCreate(Bundle savedInstanceState) { ............................................................................................. simpleExampleAdapter = new SimpleBindableAdapter<>(R.layout.simple_example_item, SimpleViewHolder.class); simpleExampleAdapter.setActionListener(new SimpleViewHolder.SimpleActionListener() { ............................................................................................. } simpleExampleRecycler.setAdapter(simpleExampleAdapter); .............................................................................................. }
      
      





, , . ViewHolder.



非表示のテキスト
 public class SimpleViewHolder extends BindableViewHolder<Integer> { @Bind(R.id.simple_example_item_tittle) TextView tittle; private int position; private SimpleActionListener simpleActionListener; public SimpleViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } @Override public void bindView(int position, Integer item, ActionListener actionListener) { super.bindView(position, item, actionListener); this.position = position; simpleActionListener = (SimpleActionListener) actionListener; tittle.setText(String.valueOf(item)); } @OnClick(R.id.simple_example_item_move_to_top) protected void OnMoveToTopClick() { if (simpleActionListener != null) { simpleActionListener.onMoveToTop(position); } } @OnClick(R.id.simple_example_item_remove) protected void OnRemoveClick() { if (simpleActionListener != null) { simpleActionListener.OnRemove(position); } } @OnClick(R.id.simple_example_item_up) protected void OnUpClick() { if (simpleActionListener != null) { simpleActionListener.OnUp(position); } } @OnClick(R.id.simple_example_item_down) protected void OnDownClick() { if (simpleActionListener != null) { simpleActionListener.OnDown(position); } } public interface SimpleActionListener extends ActionListener { void onMoveToTop(int position); void OnRemove(int position); void OnUp(int position); void OnDown(int position); } }
      
      







. .



, , .



PS。 100% , . , - .



PSS. , BindableAdapter RecyclerView, , . , , .



All Articles