1. Function Description

At present, it can only support three pictures and supports vertical and horizontal screen mode. Swipe to the next card and click to switch to the current card. In addition, the selected card will be at the top of the entire ViewGroup and will be enlarged. The most basic Android custom controls, big god don’t look at. Take a look at the image first: portrait mode is supported





gif



Landscape mode is also supported:







gif2

attribute describe The default value
scc_anim_duration The card magnifies the animation time 300
scc_edge The distance between the top and bottom edges of each card 60
scc_type Portrait or landscape mode VERTICAL
scc_min_change_distance The minimum finger slide distance will turn the page 20

The main purpose is to get familiar with the basic measurement and layout methods of custom controls. In fact, it is more convenient to use LinearLayout or FrameLayout, but at this time we do not need to rewrite onMeasure and onLayout methods. Supported custom attributes:

attribute describe The default value
scc_anim_duration The card magnifies the animation time 300
scc_edge The distance between the top and bottom edges of each card 60
scc_type Portrait or landscape mode VERTICAL
scc_min_change_distance The minimum finger slide distance will turn the page 20

2. Implementation principle

The three views (any three controls) in the ViewGroup are measured according to the preset margin and padding, and then the positions of the three views are determined according to the edge value. We didn’t use Canvas, Path, or Paint. No, we just need to change the drawing order of the child views, detect the user’s swipe or invalidate the redraw, and draw the View selected by the user last so that the currently selected View can be placed at the top. This way the view in the election will not be covered.







The principle of

3. Code explanation

A. Change the drawing order of subviews

/** * get the order of dispatchDraw, */ @override protected int getChildDrawingOrder(int childCount, Int I) {//currentItemIndex: position of the selected View in the ViewGroup if (currentItemIndex < 0) {return I; } if (i < (childCount - 1)) { if (currentItemIndex == i) i = childCount - 1; } else { if (currentItemIndex < childCount) i = currentItemIndex; } return i; }Copy the code

B. Measure the subview size

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * widthMode = MeasureSpec. GetMode (widthMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); / / setMeasuredDimension(sizeWidth, sizeHeight); int childCount = getChildCount(); int childWidth, childHeight; / * * because each child View wide high together are the same so calculate each View wide high * / if (ShapeType. VERTICAL. The ordinal () = = mShapeType) {/ / VERTICAL pattern childWidth = getMeasuredWidth() - padding*2; childHeight = getMeasuredHeight() - padding*2 - edge*2; }else{// Horizontal mode childWidth = getMeasuredWidth() -padding *2 - edge*2; childHeight = getMeasuredHeight() - padding*2; } int childWidthMeasureSpec = 0; int childHeightMeasureSpec = 0; View for (int I = 0; i < childCount; i++) { View childView = getChildAt(i); MeasureChild (childView, widthMeasureSpec, heightMeasureSpec); measureChild(childView, widthMeasureSpec, heightMeasureSpec); / * * with a precise value to measure the width of the child View. * / childWidthMeasureSpec = MeasureSpec makeMeasureSpec (childWidth, MeasureSpec. EXACTLY); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); childView.measure(childWidthMeasureSpec,childHeightMeasureSpec); }}Copy the code

C. Measure the position of sub-view

The most basic principle of position determination:







onlayout

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childCount = getChildCount(); View for (int I = 0; i < childCount; i++) { View childView = getChildAt(i); Int measureL = 0, measurelT = 0, measurelR = 0, measurelB = 0; If (ShapeType. VERTICAL. The ordinal () = = mShapeType) {/ / VERTICAL mode switch (I) {case 0: measureL = padding; measurelT = padding; measurelB = childView.getMeasuredHeight() + padding; measurelR = childView.getMeasuredWidth() + padding; childView.layout(measureL, measurelT, measurelR, measurelB); break; case 1: measureL = padding; measurelT = padding + edge; measurelB = childView.getMeasuredHeight() + padding + edge; measurelR = childView.getMeasuredWidth() + padding; childView.layout(measureL, measurelT, measurelR, measurelB); break; case 2: measureL = padding; measurelT = padding + edge*2; measurelB = childView.getMeasuredHeight() + padding + edge*2; measurelR = childView.getMeasuredWidth() + padding; childView.layout(measureL, measurelT, measurelR, measurelB); break; }}else{// Horizontal mode switch (I){case 0: measureL = padding; measurelT = padding; measurelB = childView.getMeasuredHeight() + padding; measurelR = childView.getMeasuredWidth() + padding; childView.layout(measureL, measurelT, measurelR, measurelB); break; case 1: measureL = padding + edge; measurelT = padding; measurelB = childView.getMeasuredHeight() + padding; measurelR = childView.getMeasuredWidth() + padding + edge; childView.layout(measureL, measurelT, measurelR, measurelB); break; case 2: measureL = padding + edge*2; measurelT = padding; measurelB = childView.getMeasuredHeight() + padding; measurelR = childView.getMeasuredWidth() + padding + edge*2; childView.layout(measureL, measurelT, measurelR, measurelB); break; }}}}Copy the code

D. gesture interaction logic

I used handler to send page-turning messages in order to prevent frequent trigger page-turning during finger swiping.

/** * onTouchEvent() is used to handle events, * @param event * @return */ @override public Boolean onTouchEvent(MotionEvent event) { Log.d("danxx", "onTouchEvent"); // return super.onTouchEvent(event); /** / int Y; If (ShapeType. VERTICAL. The ordinal () = = mShapeType) {/ / VERTICAL screen mode take Y coordinate Y = (int) event. GetRawY (); }else{ y = (int) event.getRawX(); } switch (event.getAction()) {case motionEvent.action_down: log. I (TAG, "motionEvent.action_down "); LastY = y; lastY = y; break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "MotionEvent.ACTION_MOVE"); Int m = y - lastY; int m = y - lastY; If m (m > 0 && > changeDistance) {/ / finger sliding down Or is left slide changeHandler removeMessages (MSG_UP); changeHandler.sendEmptyMessageDelayed(MSG_UP, animDuration); } else if (m < 0 && math.h abs (m) > changeDistance) {/ / fingers slide up or right slide changeHandler removeMessages (MSG_DOWN); changeHandler.sendEmptyMessageDelayed(MSG_DOWN, animDuration); } this.lasty = y; break; case MotionEvent.ACTION_UP: Log.i(TAG, "MotionEvent.ACTION_UP"); break; } return true; }Copy the code

D. Page code up and down or left and right

/** * return true if the page is turned successfully, False */ private Boolean downPage(){if(1 == currentItemIndex){FocusAnimUtils. AnimItem (getChildAt(currentItemIndex), False, 1.0 f, animDuration); CurrentItemIndex = 2; postInvalidate(); FocusAnimUtils. AnimItem (getChildAt (currentItemIndex), true, 1.06 f, animDuration); return true; }else if(0 == currentItemIndex){FocusAnimUtils. AnimItem (getChildAt(currentItemIndex), false, 1.0f, animDuration); CurrentItemIndex = 1; postInvalidate(); FocusAnimUtils. AnimItem (getChildAt (currentItemIndex), true, 1.06 f, animDuration); return true; }else if(2 == currentItemIndex){ return false; } return false; } /** * returns true on success, False */ private Boolean upPage(){if(1 == currentItemIndex){FocusAnimUtils. AnimItem (getChildAt(currentItemIndex), False, 1.0 f, animDuration); CurrentItemIndex = 0; postInvalidate(); FocusAnimUtils. AnimItem (getChildAt (currentItemIndex), true, 1.06 f, animDuration); return true; }else if(0 == currentItemIndex){ return false; }else if(2 == currentItemIndex){FocusAnimUtils. AnimItem (getChildAt(currentItemIndex), false, 1.0f, animDuration); currentItemIndex = 1; postInvalidate(); FocusAnimUtils. AnimItem (getChildAt (currentItemIndex), true, 1.06 f, animDuration); return true; } return false; }Copy the code

4. XML layout file

<? The XML version = "1.0" encoding = "utf-8"? > <danxx.library.widget.StackCardContainer xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/threeDViewContainer" app:scc_anim_duration="300" app:scc_edge="90" app:scc_padding="70" app:scc_type="horizontal" app:scc_min_change_distance="20" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="420dp"> <android.support.v7.widget.CardView android:id="@+id/view1" app:cardCornerRadius="10dp" app:cardElevation="10dp" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent"  android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/card_view_bg0"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" Android :text=" 6dp" Android :textSize="22sp" Android :lines="1" Android :gravity="center" android:layout_gravity="bottom" android:background="#CAC26F"/> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:id="@+id/view2" app:cardCornerRadius="10dp" app:cardElevation="10dp" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent"  android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/card_view_bg1"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" Android :textSize="22sp" Android :gravity="center" Android :lines="1" android:layout_gravity="bottom" android:background="#0085BA"/> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:id="@+id/view3" app:cardCornerRadius="10dp" app:cardElevation="10dp" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent"  android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/card_view_bg2"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" Android: float =" float "Android: float =" float" Android: float =" float "android: float =" float" android: float =" float "android: float =" float" android:layout_gravity="bottom" android:background="#4EC9AD"/> </android.support.v7.widget.CardView> </danxx.library.widget.StackCardContainer>Copy the code

In fact, we put three CardViews in our customized StackCardContainer, and click events and data binding are completely set and bound by the user. The StackCardContainer custom control simply changes the layout of the child View and handles gesture interactions.

5. Source code:GitHub project address