SlideStackViewたたはAndroidのViewGroupの拡匵

Android甚のメヌルアプリケヌションの開発䞭、Mail.Ruでは、プログラム内の゚ンドナヌザヌにずっおナビゲヌションがどれほど䟿利であるかを分析するこずがよくありたす。 モバむルアプリケヌションを䜿甚するすべおの人が、盎感的で高速なナビゲヌションを提䟛しない補品は、それを凊理する補品にずっお倱われるこずを知っおいるため、これがどれほど重芁かを説明する䟡倀はないず思いたす。 利䟿性ず慎重に考え抜かれたナビゲヌションは、ナヌザヌがアプリケヌションを気に入っおくれるか、毎回、床で携垯電話を壊さないように信じられないほどの努力を芋せたす。



アプリケヌションでは、Androidの公匏uiパタヌンに含たれる前から「スラむドメニュヌ」を䜿甚しおいたした。 ナヌザビリティラボでのいく぀かの調査の埌、ナヌザヌにより良いナヌザヌ゚クスペリ゚ンスを提䟛するために、サむドメニュヌに進んで改善する必芁があるず刀断したした。



だから、ポむントに近い。 フォルダヌずアカりントを含むフラグメントが文字のリストをスワむプしお䞀緒に移動するようなメニュヌではなく、次のペヌゞを衚瀺するために移動するシヌトのスタックなど、フラグメントを順番に移動させたいず考えたした。













SlideStackViewず呌ばれるコントロヌルの動䜜に加えお、開発偎ず補品偎の䞡方からさらにいく぀かの芁件がありたした。



1.各スラむドはフラグメントで衚す必芁がありたす

2.「バりンドオヌバヌスクロヌル効果」を実装する必芁がありたす-スラむドが端から匟力がある堎合

3.スラむド内の垂盎リストは、動䜜の䞍具合なしでスクロヌルするはずです。

4.保存䞭\埩元状態開いおいるスラむドの䜍眮など



圓然、2番目の芁件を満たす前に、ViewGroup自䜓を実装する必芁がありたす。ViewGroupは、意図したずおりに子を管理したす。



単玔なものから耇雑なものぞず移行したす。たず、androidFrameworkにアクセスする方法を決定する必芁がありたす。これにより、たず自転車を䜜成せず、次に、最も類䌌したナヌザヌ゚クスペリ゚ンスを提䟛したす。 スラむドの配眮モデルが完党に異なるため、䞊蚘のViewPagerは機胜したせん。 したがっお、階局を怜玢し、ViewGroupで停止したす。



public class SlideStackView extends ViewGroup{ public SlideStackView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ... initAttributes(context, attrs); // we need to draw childs in reverse order setChildrenDrawingOrderEnabled(true); } public SlideStackView(Context context, AttributeSet attrs) { this(context, attrs, android.R.style.Theme_Black); } public SlideStackView(Context context) { this(context, null); }
      
      





ここで特別なこずは、スラむドを描くずきに非暙準の順序を䜿甚するように求めおいるこずだけです。 最初は混乱する可胜性がありたす。 問題は、スラむドに右から巊に番号を付ける必芁があるこずです。 文字0のリストに行くために最初のスラむド、近いそれは、我々はフォルダ1のリストをスラむドし、最埌に我々はアカりント2のリストでスラむドを参照しおくださいを参照しおください。 ViewGroup自䜓の番号付けでは、通垞の順序を䜿甚する方がはるかに䟿利です。぀たり、スラむドを远加するずき、さらにスラむドをさらに描画、配眮するずきなど、巊から右ぞの順序です。 ここでのポむントは、それが慣習的であるずいうこずでもなく、1行未満のコヌドで蚘述する必芁があるずいうこずです。 実際、すべおはMotionEventsの凊理に䟝存しおいたす。 それらが転送されるず、子の配列が0からchildCountの順に枡されたす。 䞊郚のスラむドが䞋郚ず重なる可胜性がある堎合、送信されたMotionEventを凊理できるスラむドを探しお、子を䞋郚から䞊郚ぞクロヌルし始める必芁がありたす。 これに぀いおは、MotionEventの凊理を開始するずきに詳しく説明したす。

したがっお、非垞に頻繁にこのメ゜ッドが必芁です。



  /** * Same as {@link #getChildAt(int)} but uses adapter's * slide order in depending on the actual child order * in the view group * @param position position of the child to be retrieved * @return view in specified */ private View getChild(int position){ return getChildAt(getChildCount() - 1 - position); }
      
      







次に、描画順序では、䜕も倉曎する必芁はありたせん。



  /** * Use this to manage child drawing order. We should draw * the slide #{lastSlide} (in adapter's data set indexes) first. Then draw * the slide with #{lastSlide - 1} and so on until the slide #0 inclusive * <p> * {@inheritDoc} */ @Override protected int getChildDrawingOrder(int childCount, int i) { //draw the last slide first. /** * __________ * __|0 | * |1 | | * | | | * | |__________| * |__________| */ return /*childCount - */i /*- 1*/; }
      
      







今から楜しい郚分です。 もちろん、スラむドを衚瀺する予定です。スラむドの数はさたざたです。 Android Framework Teamでは、このためにいわゆるアダプタヌを䜿甚するこずを決定したした。これは優れたアプロヌチなので、䜿甚したす。 そしお、私たちに最も近いのは、ViewPagerで䜿甚されるアダプタヌの実装だけです。このアダプタヌには远加の倉曎が必芁ですが、それに぀いおは埌でさらに説明したす。



  /** * Used in case we have no valid position or actual position * can not be found. */ static final int INVALID_POSITION = -1; /** * Index of the first element from adapter's data set * added to the layout of the slide stack */ private int mFirst; /** * Current selected slide position in adapter's data set */ private int mSelected; /** * A data set adapter that prepares view for the slide stack view * and is responsible for base information about the containing * data set. */ private SlideStateAdapter mAdapter; /** * {@link DataSetObserver} that indicates about changes in slides * data set */ private final DataSetObserver mObserver = new Observer(); /** * Sets the adapter for providing the SlideStackView with * slides. * @param adapter */ public void setAdapter(SlideStateAdapter adapter){ if (mAdapter != null){ mAdapter.unregDataSetObserver(mObserver); mFirst = INVALID_POSITION; mScroller.stopScrolling(); removeAllViews(); } if(adapter != null){ mAdapter = adapter; mAdapter.regDataSetObserver(mObserver); } }
      
      





クラスオブザヌバヌは、初心者にずっお次のようになりたす。

  private final class Observer extends DataSetObserver{ @Override public void onChanged() { //empty } @Override public void onInvalidated() { //empty } }
      
      







これで、衚瀺する内容ずそれを行う方法の間のリンクずしお機胜するアダプタヌができたした。



明らかに、この皮のコントロヌルの重芁なポむントの1぀は、スラむドがSlideStackView内でどのように移動するかです。 これは、割り圓おられた機胜ずこのタスクの効果的な実装に必芁なコヌドの量の䞡方の点で、かなり膚倧なタスクです。 したがっお、次の解決策は、スクロヌル凊理に関連するすべおの機胜を、そのように呌び出されるクラスに移動するこずです。



  public static class SlideScroller extends Scroller implements OnTouchListener{ private final ScrollingListener mListener; private final GestureDetector mGestureDetector; public SlideScroller(Context context, ScrollingListener listener, OnGestureListener gestureListener) { super(context); this.mListener = listener; this.mGestureDetector = new GestureDetector(context, gestureListener); mGestureDetector.setIsLongpressEnabled(false); } public void scroll(int distance, int time) { // ... } public void fling(int velocity){ ... } public void stopScrolling() { ... } @Override public boolean onTouch(View v, MotionEvent event) { ... } void finishScrolling() { ... } boolean isScrolling(){ ... } boolean isJustifying(){ ... } boolean isTouchScrolling(){ ... } }
      
      







これで、このモゞュヌルのスクロヌルキッチン党䜓を安党に取り出し、ScrollingListenerを介しおSlideStackViewに必芁なむベントのみを通知できたす。



  public interface ScrollingListener { void onScroll(int distance); void onStarted(); void onFinished(); void onJustify(); }
      
      







ViewGroupの実装はどこから始たりたすか ViewGroupは、Viewでできるこずに加えお、その䞭に他のビュヌを持぀こずもできるリンカヌです。 したがっお、私たちの質問に察する答えは、ViewGroupの実装であり、これはOverrideメ゜ッドで始たりたす。

  @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { }
      
      





スラむドを配眮する堎所

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { }
      
      





そしお、スラむドを枬定する堎所。

枬定から始めたしょう

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); int childHeightSize = getMeasuredHeight(); int mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec( childHeightSize, MeasureSpec.EXACTLY); // Make sure we have created all fragments that we need to have shown. mInLayout = true; fillViewsIn(); mInLayout = false; // measure slides int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChild(i); if (child.getVisibility() != GONE) { int childWidthSize = getMeasuredWidth() - ( getRightEdgeDelta(mFirst + i - 1) + getLeftEdge(mFirst + i)); final int widthSpec = MeasureSpec.makeMeasureSpec( childWidthSize, MeasureSpec.EXACTLY); // LOG.v("Measuring #" + i + " " + child // + ": " + widthSpec); child.measure(widthSpec, mChildHeightMeasureSpec); } } }
      
      







ここではすべおが簡単です。最初に、コンテナから䞎えられた仕様に基づいおスラむドスタックのサむズを蚭定したす。 モバむルメヌルでは、スラむドスタックがマヌクアップのルヌト芁玠であるため、この方法で利甚可胜な領域党䜓を埋めたす。



なぜなら スラむドの高さを倉えたくない堎合は、MeasureSpec.EXACTLYフラグず、スラむドスタックの枬定された高さに等しい倀を䜿甚しお、高さの仕様を䜜成したす。



スラむドを枬定するには、圓然スラむド自䜓が必芁なので、それらが既にマヌクアップに远加されおいるこずを確認する必芁がありたす。 これを行うには、スラむドを䞊から䞋に埋めたす。 その埌、スラむドを調べお、目的の幅を決定し、child.measurewidthSpec、mChildHeightMeasureSpecを呌び出しお枬定したす。



スラむドの幅は、スラむドスタックの幅から特定のスラむドフォルダヌのリストを含むスラむドなどの巊右のむンデントを匕いたものずしお定矩されたす。







スラむドを枬定した埌は、正しく配眮するだけです。



  @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mInLayout = true; fillViewsIn(); mInLayout = false; for (int i = 0; i < getChildCount(); i++) { View child = getChild(i); int position = i + mFirst; onLayoutChild(child, position, changed); } mDirty.setEmpty(); } /** * Layouts child at the specified position (in adapter's data set). * Measures the child if needed. * @param child a child we are going to layout * @param position position of the child in adapter's data set */ protected void onLayoutChild(View child, int position, boolean changed) { if (child.getVisibility() != GONE) { LOG.d("onLayoutChild " + position); if (position < mSelected && changed){ closeView(position); LOG.v("close slide at " + position); } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); SlideInfo info = getSlideInfo(child); int childLeft = getLeftEdge(position) + info.mOffset; int childRight = getRightEdge(position - 1) + info.mOffset; int childTop = getTop(); if (lp.needsMeasure) { lp.needsMeasure = false; final int widthSpec = MeasureSpec.makeMeasureSpec( childRight - childLeft, MeasureSpec.EXACTLY); final int heightSpec = MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY); child.measure(widthSpec, heightSpec); } // LOG.v("Positioning #" + position + " " + child + ":" + childLeft // + "," + childTop + " " + child.getMeasuredWidth() + "x" // + child.getMeasuredHeight()); child.layout(childLeft, getTop(), childRight, getBottom()); } }
      
      







最初に、マヌクアップに衚瀺できるすべおのスラむドが既に远加されおいるこずを確認する必芁がありたす。次に、特定の各䜍眮にスラむドの䜍眮を盎接蚭定したす。 远加したばかりのスラむドが閉じられない可胜性があるずいう事実を考慮するこずが重芁です;これを修正する必芁がありたす。



たた、スラむドスタックの枬定埌にスラむドを远加した堎合、このスラむドも枬定する必芁がありたす。



ここで、SlideInfoは、スラむドの䜍眮ず状態に関する情報を含む通垞のホルダヌです。



  /** * Simple info holder for the slide item * @author k.kharkov */ public static final class SlideInfo implements Parcelable{ /** * Describes slide offset relative to the slide stack. * Note that offset value do not describe actual slide * position inside the slide stack but only shows the * offset relative to the left position of the slide. * <p> * This means * <code>getLeftEdge(position) + info.mOffset</code> * equals to actual offset of the slide relative to * the slide stack view. */ private int mOffset = 0; /** * Indicates whether the slide is visible to the user * or hidden at near the slide stack side */ private boolean mOpened = true; /** * Position of the slide inside the slide stack */ private int mPosition = INVALID_POSITION; /** * The drawable to fill space between this slide and the * previous one. * @see SlideStackView#fillAreaToPrevSlide(Canvas, View) */ private Drawable mSpace; public SlideInfo() { super(); } }
      
      







実際、SlideInfoを個別に䜿甚する代わりに、LayoutParamsクラスの盞続人に察凊するこずができたす。これは最終的には将来も䜿甚したす。 執筆時点では、これを知りたせんでしたが、珟圚この情報をlayoutParamsに転送しおいたせんが、すぐにそれをやりたいず思いたす。 远加ホルダヌには犯眪者はいたせんが、私はKISSKeep It Simple Stupidアプロヌチの支持者であり、2぀ではなく1぀のオブゞェクトを䜿甚する方がはるかに簡単です:)



そこで、ViewGroupに既に远加したスラむドをどのように扱うかを考えたした。 ただ怜蚎されおいない質問は、そこにそれらを远加する方法です。 このため、メ゜ッドを残したした



  /** * Adds all required views in layout first. * Then adjusts visibility for each child. * @see #addViewsInside(int, int) * @see #adjustViewsVisibility() */ private void fillViewsIn() { int position = 0; int left = 0; if (getChildCount() > 0){ position = getLastVisiblePosition() + 1; View lastView = getChild(position - mFirst - 1); left = lastView.getLeft() - lastView.getScrollX(); } addViewsInside(position, left); adjustViewsVisibility(); }
      
      







このメ゜ッドは、最埌のスラむドを怜玢し、次のスラむドがあればSlideStackViewに远加し、ナヌザヌに衚瀺されたす。 それはすべおこのように起こりたす



  /** * Uses the adapter to add views to the slide stack view. * @param position last visible position of the view * @param left left coordinate of the last visible slide */ private void addViewsInside(int position, int left) { if (mAdapter == null || mAdapter.getCount() == 0){ return; } mAdapter.startUpdate(this); while ((position <= mSelected + 1 || left > getLeft()) && position < mAdapter.getCount()){ if (mFirst == INVALID_POSITION){ mFirst = 0; } LOG.d("addView inside " + position + " mSelected " + mSelected); mAdapter.instantiateItem(this, position); left = mSelected > position ? getLeftEdge(position) : getRightEdge(position - 1); position ++; } mAdapter.finishUpdate(this); }
      
      







スラむドずしおフラグメントを䜿甚するこずにしたので、1぀のフラグメントトランザクション内にスラむドを远加する方が適切です。



その埌、アカりントに新しく远加されたスラむドを取っお、初期条件の改正を行い、プロセスを繰り返しおスラむドするたでスラむドを䜜成したり、䞍足する、たたはスラむドは、芖認性のスラむドスタックの境界を越えお行くこずはありたせん。 leftが<= getLeftになった堎合、これは远加した最埌のスラむドがスタックスラむドの境界線ず重なっおいるこずを意味したす。 次のスラむドが完党にこのスラむドの䞋のいずれかであり埗る、たたは巊に芋るこずができるが、この領域は、スラむドスタックの境界内に収たっおいないずナヌザが衚瀺されおいないこずが、この手段。 そしお以来 スラむドはナヌザヌには衚瀺されないため、远加する意味はただありたせん。



「しかし、スラむド自䜓の远加はどこですか」ずあなたは尋ねたす。 mAdapter.finishUpdateこれを呌び出した埌; アダプタはトランザクションを開始し、FragmentManagerは転送されたコンテナこれ-぀たりSlideStackViewぞのフラグメントの远加を開始したす。 フラグメントを远加するプロセスを䞀蚀で説明するのは難しいので、読者がレビュヌできるようにこのトピックを残したしょう。 スラむドを正しく配眮するには、いく぀かの調敎が必芁になるため、このメ゜ッドを再定矩したす。

  /** * Specifies correct layout parameters for the child and * adds it according the the current {@link #mInLayout} * status. * <p> * {@inheritDoc} */ @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { LOG.d("Add view from outside " + child); if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } final LayoutParams lp = (LayoutParams) params; SlideInfo info = getSlideInfo(child); info.mPosition = getChildCount(); if (mAdapter != null){ info.mSpace = mAdapter.getSpaceDrawable(info.mPosition); } if (mInLayout) { lp.needsMeasure = true; addViewInLayout(child, 0, params); } else { super.addView(child, 0, params); } if (info.mPosition < mSelected){ closeView(info.mPosition); } }
      
      







必芁なデヌタを入力したす。 次に、レむアりト䞭にスラむドが远加されるかどうかに応じお、スタックをスラむドに远加し、その䜍眮が珟圚アクティブなスラむドの䜍眮よりも小さい堎合はスラむドを閉じたす。



圓然、フラグメントをスラむドスタックに远加する堎合は、い぀かそれらを削陀する必芁がありたす。 削陀動䜜は远加に䌌おいたすが、ナヌザヌに衚瀺されなくなったスラむドを探し、アダプタヌを䜿甚しおViewGroupから削陀するだけです。



メ゜ッドfillViewsIn; 最埌の保留䞭の行が残り、そこでadjustViewsVisibilityメ゜ッドを呌び出したす。



このメ゜ッドは、名前が瀺すように、スラむドの可芖性を修正したす。 これは、マヌクアップから削陀できないが、ナヌザヌには衚瀺されなくなったスラむドの描画に時間を無駄にしないために必芁です。 それ自䜓で、芖認性の調敎は非垞に簡単です - 我々は唯䞀View.setVisibilityint型、パスたたはView.VISIBLE、たたはView.INVISIBLEを通じおスラむドに可芖性を眮きたす。 メ゜ッド自䜓は少し耇雑に芋えたすが、その動䜜の原理を理解するず、すべおが明らかになりたす。



  /** * Sets visibility parameter for each child according * to the actual visibility on the screen. Takes into * account that child shouldn't be invisible if it's * shadow is visible on the screen because it would * prevent from triggering {@link #drawChild(Canvas, View, long)} * method over that child. */ private void adjustViewsVisibility() { /** * init the rect to align right edge if the slide stack view */ Rect drawingRect = new Rect(); drawingRect.left = getRight(); drawingRect.top = getTop(); drawingRect.right = getRight(); drawingRect.bottom = getTop() + getHeight(); Rect parent = getChildDrawingRectPositive(this); /** * Then union each child drawing rect * with drawingRect of the slide stack * in order to determine when the views * behind the last actually visible view * in the slideStack view and hide all * the following views in order to prevent * re-drawing non-visible views * ________________________________ * | slideStackView __________| * | ____________________| | * || _ _ _ _ | | * ||| | |visible | * || |slide #0 | * ||| hidden slide| | | * || #2 | | * |||_ _ _ _| | | * ||last actual visible | | * ||slide #1 |__________| * ||__________________________| | * |________________________________| */ for (int i = 0; i < getChildCount(); i ++){ boolean hideNext = false; // LOG.v("adjustVisibility " + i); View child = getChild(i); Rect childRect = getChildDrawingRectPositive(child); // LOG.v("child.left " + childRect.left + // " drawing.left" + drawingRect.left + // " parent " + parent.toShortString() + // " child" + childRect.toShortString()); if (childRect.left < drawingRect.left && Rect.intersects(parent, childRect)){ drawingRect.union(childRect); // LOG.d("drawingRect after union with child " + i + " = " + // drawingRect.toShortString()); hideNext = false; } else { // LOG.d("hide view " + i); // LOG.v("child " + childRect.toShortString() + // " drawing " + drawingRect.toShortString() + // " parent " + parent.toShortString()); Rect childShadow = getShadowRect(childRect); if (childShadow.isEmpty()){ hideNext = true; } // if shadow visible - do not hide the slide // and let the shadow to be drawn if (childShadow.left < drawingRect.left && Rect.intersects(parent, childShadow)){ hideNext = false; } else { hideNext = true; } } // LOG.d("set visibility for child " + i + " = " + // (hideNext ? "Invisible" : "Visible")); child.setVisibility(hideNext ? View.INVISIBLE : View.VISIBLE); } }
      
      







このメ゜ッドの動䜜をペむントするために、できるだけ簡単にステップごずに詊みたす。



䞀般的なアルゎリズムは次のようになりたす。



1.スタックスラむドの右端を取りたす右端のスラむドは垞に完党に衚瀺され、巊偎のスラむドはそれず重なっおいるためです。逆に、それはできたせん。

2.すべおのスラむドを調べお、各スラむドが占めるスラむドスタック内のスペヌスを占有゚リアに远加したす。

3.同時に、次のスラむドがスタックスラむドの境界倖にあるか、その䞊にあるスラむドによっお完党に閉じられおいるかを調べ、「非衚瀺」ずしおマヌクしたす。 それ以倖の堎合、スラむドは「可芖」ず芋なされたす。



䞊蚘のdrawingRectメ゜ッドでは、これはたさにスラむドで既に占有されおいる領域であり、childRectはスラむドが特定の䜍眮にある領域です。



圱の調敎を行う必芁がありたす。 圱を自分で描画するため、スラむドの可芖性を調敎するずきに、圱が占めるスペヌスを考慮しないず、スラむドが互いに衚瀺/非衚瀺される瞬間に䞍快な点滅が芋られたす。 問題は、描画プロセス䞭にViewGroupで、子が衚瀺されおいるか衚瀺されおいないかを確認するこずです。 衚瀺されおいない堎合、drawChildCanvas、View、longメ゜ッドはこの子に察しお呌び出されたせん。これは非垞に論理的です。 したがっお、スラむドからは圱だけが芋える堎合でも、スラむドが芋えるようにする必芁がありたす。 これを行うには、远加のチェックを行い、スラむドがナヌザヌに衚瀺されないず刀断した時点で修正を行いたす。

  Rect childShadow = getShadowRect(childRect); if (childShadow.isEmpty()){ hideNext = true; } // if shadow visible - do not hide the slide // and let the shadow to be drawn if (childShadow.left < drawingRect.left && Rect.intersects(parent, childShadow)){ hideNext = false; } else { hideNext = true; }
      
      





䞀般に、圓然ながら、adjustViewsVisibilityメ゜ッド党䜓が最適化のために実装されおいるずいう事実に泚意するこずが重芁です。 結局のずころ、ナヌザヌのモバむルデバむスに、ただ芋えない仕事をさせたくないのです。 さらに、䞍必芁な描画はパフォヌマンスに倧きく圱響し、それに応じおアニメヌションをスムヌズにしたす。



ただし、ナヌザヌに衚瀺されないスラむドの描画を防止するだけでは、問題が完党に解決するわけではありたせん。 スラむドの「オヌバヌレむ」はただありたす。 これは、1぀のスラむドを暪に移動し、その䞋から次のスラむドの䞀郚を芋るずきに発生したす。 ぀たり、ナヌザヌにはスラむドの䞀郚のみたたは、シャドりからシャドりの䞀郚のみが衚瀺され、スラむド党䜓が党䜓ずしお描画されるこずがわかりたす。 その埌、もちろん、その䞊に別のスラむドを描画したす。



個人的に、Canvas.clipRectRectメ゜ッドを䜿甚する以倖に、このような問題を解決する他の方法は知りたせん。 したがっお、個々のフレヌムでナヌザヌがスラむドのどの郚分を芋るかを決定し、キャンバス䞊に描画する領域をナヌザヌがこのスラむドから芋るスペヌスに制限しおから、スラむドを描画する必芁がありたす。



䞊蚘のスラむド描画方法は、私にずっお次のようなものです。

  /** * Clips the canvas to the child current bounds plus shadow. * <p> * Draws the shadow as well for each child. * <p> * {@inheritDoc} * @see #applyShadowToChild(Canvas, View) */ @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { Rect childClip = getChildClipRect(child); // LOG.d("canvas " + canvas.getClipBounds().toShortString()); // LOG.d("draw Child " + child + " clip = " + childClip.toShortString()); // first // draw spaces between slides fillAreaToPrevSlide(canvas, child); // second // draw shadow for the slide applyShadowToChild(canvas, child); // third // actually draw child try{ canvas.save(); canvas.clipRect(childClip); boolean r = super.drawChild(canvas, child, drawingTime); return r; } finally { canvas.restore(); } }
      
      





すべおは、スラむドの長方圢を蚈算するこずから始たりたす。これは、画面䞊でナヌザヌに衚瀺され、その埌、スラむド間のスペヌスを埋めお存圚する堎合、スラむドの圱を描画し、その埌、蚈算されたchildClipにキャンバスを制限し、描画したすViewGroupで定矩されおいるずおりにスラむドしたす。



ご芧のずおり、すべおが非垞に簡単です。 すべおの魔法は、キャンバスを「切り抜く」必芁がある領域を数える方法の䞭にありたす。

  /** * Retrieves children's visible position on the screen * without it's shadow. * @param child we should retrieve visible bounds for * @return a child's visible bounds */ private Rect getChildClipRect(View child) { Rect childClip = getChildDrawingRectPositive(child); int position = getPositionForView(child); subtractRectWithChilds(childClip, position); // LOG.v("child clip " + position + " " + childClip.toShortString()); return childClip; } /** * Changes the specified clip rectangle, to subtract all the * following children from it. * @param childClip initial child rectangle in the screen * @param position child position within adapter's data set * @see #subtractToLeft(Rect, Rect) */ private void subtractRectWithChilds(Rect childClip, int position) { if (position >= 0){ position -= mFirst; for (int i = position - 1; i >= 0; i --){ View c = getChild(i); Rect r = getChildDrawingRectPositive(c); boolean changed = subtractToLeft(childClip, r); if (changed){ // LOG.v("child clipped " + childClip.toShortString()); } } } }
      
      





最初の方法では、スラむドスタック内のスラむドの䜍眮を芋぀けおから、スラむドアダプタヌのむンデックスでその䜍眮を芋぀けたす。 その埌、䞍芁なものをすべお「枛算」しお結果を返したす。



, «» . , , ( ).



, .



, , , .

  /** * Same as {@link #subtract(Rect, Rect)} method, but processes * the case where source rectangle wasn't changed because it * contains <code>r</code>. In this case it adjusts <code>r</code> * from this: * <pre> * _______________________ * | source _________ | * | | r | | * | | | | * | | | | * | | | | * | |_________| | * |_______________________| * </pre> * * to this: in order to leave only left side of the source rectangle. * <pre> * ___________________________ * | source | r |1px| * | | |<->| * | | | | * | | | | * | | | | * | | | | * | | | | * |_________|_____________|___| * </pre> * @param source the rectangle we are going to subtract from * @param r the rectangle we are going to subtract from * source * @return <code>true</code> in case the source rectangle * has been changed. <code>false</code> otherwise */ private static boolean subtractToLeft(Rect source, Rect r){ boolean changed = subtract(source, r); if (!changed && source.contains(r)){ // adjust intersected rect manually r.right = source.right + 1; r.top = source.top; r.bottom = source.bottom; changed = subtract(source, r); } return changed; } /** * Subtracts <code>r</code> from the <code>source</code>. * Sets <code>r</code> rectangle to the intersection as well. * @param source * @param r * @return <code>true</code> if rectangle has been subtracted, * <code>false</code> otherwise. */ private static boolean subtract(Rect source, Rect r) { if (source.isEmpty() || r.isEmpty()){ return false; } if (r.contains(source)){ // LOG.w("return empty rect"); source.setEmpty(); return true; } if (source.contains(r)){ return false; } if (!r.intersect(source)){ return false; } boolean changed = false; if (source.left == r.left){ source.left = r.right; changed = true; } if (source.right == r.right){ source.right = r.left; changed = true; } if (source.top == r.top){ source.top = r.bottom; changed = true; } if (source.bottom == r.bottom){ source.bottom = r.top; changed = true; } source.sort(); return changed; }
      
      





, , , «» - :)



, , – Rect. , . . , . «» , - , . , . . , , , . , , .



, , , , .



, – . , . , . , , MotionEvent' , , -, .



, , , ViewGroup.



– - Mail.Ru. . , .



All Articles