RecyclerViewで視差ヘッダーを作成する

ご挨拶!

材料設計の出現により、新しい要素も登場します。 たとえば、RecyclerViewが登場しました。 ハブラーでの彼について、かつて書いた以上のこと: タイツツッツ



それを使用することは理解できるように思えますが、もっと欲しいです。 通常、新しい選択肢に切り替えると、何かが欠けています。 それで、私は何が十分にありませんでした。 特定のアプリケーションページでGoogle Playのように視差効果を作成する必要がありました。 ListViewおよびScrollViewの実装が利用可能です。 私は偉大で強力なものを見ましたが私が見つけたのはこのリポジトリだけでした。 ソリューションは機能しているようで、人々はそれを使用しています。 しかし、私はその使いやすさが好きではありませんでした。 そしていつものように、自分で書くことにしました。



一般的に、単純なものから始めました。つまり、ヘッダーをサポートするアダプターを作成する必要があるという事実です。 このアダプターは、R​​ecyclerViewの従来のアダプターの原則と異なるものであってはなりません。



RecyclerView.Adapterクラスには、各位置の型を返すpublic int getItemViewType(int position)メソッドがあり、デフォルトでは常に0を返します。



結果のクラスは抽象クラスになることをすぐに警告しなければなりません。 また、それぞれいくつかの方法もあります。



次のように再定義します。



public static final int TYPE_VIEW_HEADER = Integer.MAX_VALUE; private int sizeDiff = 1; @Override public final int getItemViewType(final int position) { if (position == 0 && enableHeader) return TYPE_VIEW_HEADER; return getMainItemType(position - sizeDiff); } protected abstract int getMainItemType(int position);
      
      





getMainItemTypeでの偶発的なヒットを避けるために、値TYPE_VIEW_HEADERがそのように選択されました

論理的には、Viewの作成と情報の表示を担当するメソッドと、いくつかの抽象メソッドを実装する必要があります。

非表示のテキスト
  @Override public final RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { if (viewType == TYPE_VIEW_HEADER) return onCreateHeaderViewHolder(parent); return onCreateMainViewHolder(parent, viewType); } protected abstract HeaderHolder onCreateHeaderViewHolder(final ViewGroup parent); protected abstract VH onCreateMainViewHolder(final ViewGroup parent, final int viewType); @Override public final void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { if (holder.getItemViewType() == TYPE_VIEW_HEADER) { onBindHeaderViewHolder((HeaderHolder) holder); return; } onBindMainViewHolder((VH) holder, position - sizeDiff); } protected abstract <HH extends HeaderHolder> void onBindHeaderViewHolder(final HH holder); protected abstract void onBindMainViewHolder(final VH holder, final int position); protected static class HeaderHolder extends RecyclerView.ViewHolder { public HeaderHolder(final View itemView) { super(itemView); } }
      
      









はい、確かにキャスティングはあまり見栄えが良くありませんが、それらを使わずにそのままにするより良い方法を思いつきません。



上記のコードの機能について簡単に説明します。 まず、 getItemViewTypeメソッドで目的のタイプを指定し、それに基づいてonCreateViewHolderで目的のViewHolderを作成し、 viewTypeも確認してonBindViewHolderのデータをバインドします。



すでに書かれているものは、ヘッダーをより一般的にするための機能をすでに提供していますが、記事のタイトルはもっと約束しました。



したがって、継続します。



ヘッダーが表示されるので、移動させましょう。 ただし、RecyclerViewのコンテンツの動きと反対方向に動く必要があります。



これを行うには、内容を指定された量だけ移動できる補助コンテナが必要です。 これは、アダプターの内部クラスになります。



このクラスのコード
  private static class CustomWrapper extends FrameLayout { private int offset; public CustomWrapper(Context context) { super(context); } @Override protected void dispatchDraw(Canvas canvas) { canvas.clipRect(new Rect(getLeft(), getTop(), getRight(), getBottom() + offset)); super.dispatchDraw(canvas); } public void setYOffset(int offset) { this.offset = offset; invalidate(); } }
      
      







次に、渡されたビューを奇跡コンテナに配置するように、 HeaderHolderクラスを書き換えます。



更新されたHeaderHolder
  protected static class HeaderHolder extends RecyclerView.ViewHolder { public HeaderHolder(final View itemView) { super(new CustomWrapper(itemView.getContext())); final ViewGroup parent = (ViewGroup) itemView.getParent(); if (parent != null) parent.removeView(itemView); ((CustomWrapper) this.itemView).addView(itemView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); this.itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } }
      
      







これで、目的の値をCustomWrapperに送信するだけで、視差になります。 これを行うには、RecyclerViewでスクロールイベントにサブスクライブする必要があります。 これには内部クラスを使用しました。



スクロールローラー
  private class ScrollListener extends RecyclerView.OnScrollListener { @Override public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) { totalScroll += dy; if (customWrapper != null && !headerOutOfVisibleRange()) { doParallaxWithHeader(totalScroll); } changeVisibilityHeader(); } private void changeVisibilityHeader() { if (customWrapper != null) { customWrapper.setVisibility(headerOutOfVisibleRange() ? View.INVISIBLE : View.VISIBLE); } } private boolean headerOutOfVisibleRange() { return totalScroll > getHeaderHeight(); } }
      
      







ここではすべてが簡単です。 スクロールすると、onScrolledメソッドが呼び出されます。 その中で、スクロールの現在の位置を変更し、ヘッダーで何かできるかどうかを確認します。 もしそうなら、視差を行います。 そして、ヘッダーが画面の領域を超えてしまうと、これが必要ではないため、あらゆる種類の操作を停止します。



そして最後のコード挿入
  private void doParallaxWithHeader(float offset) { float parallaxOffset = offset * SCROLL_SPEED; moveHeaderToOffset(parallaxOffset); if (parallaxListener != null && enableHeader) parallaxListener.onParallaxScroll(left); customWrapper.setYOffset(Math.round(parallaxOffset)); notifyHeaderChanged(); } private void moveHeaderToOffset(final float parallaxOffset) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { customWrapper.setTranslationY(parallaxOffset); } else { TranslateAnimation anim = createTranslateAnimation(parallaxOffset); customWrapper.startAnimation(anim); } } private TranslateAnimation createTranslateAnimation(final float parallaxOffset) { TranslateAnimation anim = new TranslateAnimation(0, 0, parallaxOffset, parallaxOffset); anim.setFillAfter(true); anim.setDuration(0); return anim; } public final void notifyHeaderChanged() { notifyItemChanged(0); } public final void notifyMainItemChanged(final int position) { notifyItemChanged(position + sizeDiff); }
      
      







視差効果については、動きの「速度」を下げるだけでよいことは明らかだと思います。 このために、係数SCROLL_SPEEDが使用されます。 次に、ヘッダーを新しい受信値に移動します-それだけです。



それを使用することはすべて非常に簡単です。



ここでソースを取得できます 。 例があります。 これはすべてjCenterに公開されるため、gradleで1行で接続します。



ボーナスは、ヘッダーを備えた機能を提供するGridLayoutManagerの後継であるHeaderGridLayoutManagerです。視差もそこで機能します。



SpacesItemDecorationもあります 。これは、すべてのRecyclerView要素間の望ましい距離を設定します。 StaggeredGridLayoutManagerでまだ動作していません



すべてが好きです。 ご清聴ありがとうございました。



All Articles