UIソリティア:AndroidでStackViewを作成する

この記事では、AndroidでカスタムViewGroupを作成した経験を共有したいと思います。これは、統合フロントエンドシステムプログラムのプロジェクトの1つとして開発しました。 私たちの仕事は、銀行カードの美しいギャラリーを作成することでした。 同時に、RecyclerViewとLinearLayoutManagerが提供する通常のリストは適合しませんでした。 カードを切り替えるときにカードが画面を完全に超えずに山に集められるように、スクロールカードの非標準の仕組みを表示するというアイデアがありました。 これをどのように行ったかについては、以下を読んでください。









背景では、最初のオプションは簡単だったと言います-既製のソリューションを使用します。 たとえば、Androidには以前から似たようなStackViewコントロールがありました。 コードは提供しません。非常に簡単です。ActivityStackViewで探し、アダプターを設定し、マップのビューを提供します。 どうなるか見てみましょう。 カードは斜めに配置されており、アニメーションは少し奇妙です。 私が望むものではありません。 このクラスのカスタマイズを理解するのは長いので、自分で試してみましょう。



リストの仕組み



試行錯誤を繰り返して、メカニックに行きました。そこでは、カードはリストのような形で表示されます。 同時に、通常のリストに表示されていないカードは、それを超えるとスタックにスタックされます。 メモリの使用を制限すること、より正確には、すべての子ビューではなく、最小、できれば一定量をメモリに保持することが重要です。







簡単にするために、上にスクロールするときのスタックの仕組みを上から説明します。 下からのスタックはほぼ同じように機能します-カードだけがその下に滑り込み、オーバーランしません。 図の赤い線は、スタックの先頭の境界が通過する場所を示しています。



その後の作業では、次の表記法を導入します。





リスト状態



  1. マップ全体が画面上にあります。 ひとつずつ。 すべてが通常のリストのとおりです。
  2. 上にスクロールし始めます。 青いカードが緑のカードにぶつかり始めます。 緑色のカードの可視部分がcardFoldHeightと等しくなる位置で停止します。 今、山の中に1枚のカードがあります。
  3. 上にスクロールし続けます。 最初の2枚のカードは移動しません。 ピンクのカードが青に近づいています。 その下にある青いカードの可視部分がcardFoldHeightと等しくなると、その位置で停止します。 今、山には2枚のカードがあります。
  4. さらにスクロールします。 これで、ピンクのカードの表示部分がcardFoldHeightと等しくなります。 この状態では、山に3枚のカードがあります-許可されるカードの最大数。 スタックに新しいカードを追加するには、そこから追加された最初のカードを出力する必要があります。 厳密に言えば、スタックはFIFOの原則に従って機能します。


どの時点でスタック全体を動かして、最初のカードを船外に投げ出しますか? 可能なオプションを検討してください。



a)青緑色のカードが黄色のカードに触れた瞬間にスタックの移動を開始します。









b)ターコイズ色のカードが既に黄色に移動した時点でスタックの移動を開始し、黄色のカードのサイズはcardFoldHeight、ポイントAです。









どちらの場合も、黄色のカードがポイントAからポイントBに移動するとスタックが移動します。青緑色のカードが位置Bに落ちると、青色のカードは完全に見えなくなります。 この状態では、StackViewは青いカードが占有していたメモリを解放します。



実装では、この場合のカードの視覚的な移動がよりスムーズに見えるため、2番目のオプションを選択しました。







内部StackViewデバイス



カスタムViewGroupの基本的なコンポーネントと、それらの相互作用について簡単に説明しましょう。



ヘルパークラス





//     ,    class Range {    private int mFrom;    private int mTo; }
      
      





 //          //  currentScroll. public class RangeCalculator {   public Range getVisibleRange(int currentScroll); }
      
      





 //         //  — currentScroll. class Fold {  public int minTop();  public int maxTop();   public void update(int currentScroll, int fullCardHeight); }
      
      





今スタッカック自体



StackViewはViewGroupの子孫です。 StackViewでは、dispatchTouchEvent(MotionEventイベント)メソッドで、GestureDetector.SimpleOnGestureListenerの子孫を使用して、ユーザーがリストをスクロールするタイミングとcurrentScrollオフセットを決定します。 currentScrollパラメーターは、リスト内のカードの位置を決定します。



子ビューのサイズと位置を決定するStackViewクラスの主なメソッドは、onMeasure()とonLayout()です。 以下は、これらのメソッドの擬似コードです。



 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   super.onMeasure(widthMeasureSpec, heightMeasureSpec);   measureChildren(widthMeasureSpec, heightMeasureSpec);          mFold.update(mCurrentScroll, mFullCardHeight);       final Range newRange = mRangeCalculator.getVisibleRange(mCurrentScroll);       if (getChildCount() == 0) {           addCards(newRange);       } else {           removeCards(newRange);           addNewCards(newRange);       }       mVisibleCardsRange.set(newRange);   } }
      
      





 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {   final int childCount = getChildCount();   for (int i = 0; i < childCount; i++) {       final View child = getChildAt(i);       final int childLeft = getPaddingLeft();       final int childRight = childLeft + child.getMeasuredWidth();       final int childHeight = child.getMeasuredHeight();       final int childTop = getChildTop(childCount, i, childHeight);       final int childBottom = childTop + childHeight;       child.layout(childLeft, childTop, childRight, childBottom);   } }
      
      





 //  childTop   private int getChildTop(final int childCount, final int childIndex, int childHeight) {   int childTop = -mCurrentScroll + (childIndex + mVisibleCardsRange.from()) *   childHeight + getPaddingTop();      int minTopForCurrentChild = (int) (childIndex * mFold.getCardSizeInFold()) - mFold.minTop();   minTopForCurrentChild = Math.max(0, minTopForCurrentChild);   int maxTopForCurrentChild = (int) (getMeasuredHeight() - (childCount - childIndex) * mFold.getCardSizeInFold()) + mFold.maxTop();   maxTopForCurrentChild = Math.min(maxTopForCurrentChild, getMeasuredHeight());      if (childTop < minTopForCurrentChild) childTop = minTopForCurrentChild;   if (childTop > maxTopForCurrentChild) childTop = maxTopForCurrentChild;   return childTop; }
      
      





何が起こったのか、どのような結論を出したのか



このカスタムViewGroupコンポーネントの作成をゼロから開始するのではなく、同様のリストの実装を基礎として取りました。 しかし、私は他の誰かのコードを研究し、必要な状態に仕上げるのに時間を費やす必要がありました。 その過程で、リストの仕組みを実装するためのいくつかのオプションを作成し、最終的には美しく見えるものを選択しました。いくつかの単純化のために、限られた量のメモリを消費します-この場合、単にスタック内のカードの数を制限します



実際には、スタック内のすべてのカードを表示することは意味がないことが判明しました。 数が多すぎる場合、スタック内のカードの表示部分のサイズはゼロになる傾向があり、これは美しさを追加しません。 一定数の子ビューがあり、灰色オオカミOutOfMemoryExceptionを恐れていません。



プロトタイプを構築するタスクに対処したと想定できます。 見栄えが良く、最も重要なのは技術的に実行可能なリストメカニクスのバリアントを特定しました。 そして今、私たちはそれを改善する方法を知っています。



私たちはあなたとチャットし、トピックに関するアイデアを交換させていただきます。 投稿内のすべてのコードをアップロードするのではなく、基本的な原則について説明することにしました。 ご質問がある場合は、コメントにご記入ください。



All Articles