ã¢ããªã±ãŒã·ã§ã³ã§ã¯ã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. . , .