365bet亚洲版登录-bet官网365入口

365bet亚洲版登录拥有超过百间客房,bet官网365入口的文化历经几十年的传承和积淀形成的核心内容获得业界广泛的认可,365bet亚洲版登录是目前信誉最高的娱乐场所,同国内外几百家网上内容供应商建立了合作关系。

用ViewGroup创设一个3D堆集卡牌容器

目前只能支持三张图片,支持横竖屏模式,手指滑动翻页到下一张卡片,手指点击也可以切换到当前卡片,并且选中的卡片会在整个ViewGroup的最上层,会被放大,可以自定义放大动画的时长。最基本的Android自定义控件,大神就别看了。来先看效果图吧:支持竖屏模式

图片 1gif也支持横屏模式:图片 2gif2

属性 描述 默认值
scc_anim_duration 卡片放大动画时间 300
scc_edge 每个卡片顶边和底边的距离 60
scc_type 竖屏还是横屏模式 VERTICAL
scc_min_change_distance 手指最小滑动距离才会翻页 20

主要是想熟悉一下自定义控件的基本测量和布局方式,其实使用LinearLayout或者是FrameLayout来做会更加方便,但是这个时候就不需要我们自己去重写onMeasure和onLayout方法了。支持的自定义属性:

属性 描述 默认值
scc_anim_duration 卡片放大动画时间 300
scc_edge 每个卡片顶边和底边的距离 60
scc_type 竖屏还是横屏模式 VERTICAL
scc_min_change_distance 手指最小滑动距离才会翻页 20

把ViewGroup中的三个View(可为任意的三个控件)按照预设好的边距和padding测量大小,然后三个view根据edge值来确定依次确定位置。我们没有用到canvas、path或者paint。没必要,我们只需要改变子View的绘制顺序,检测到用户的滑动或者是点击就invalidate重绘,把用户选中的view放在最后绘制这样就可以将当前选中的view放在最上层。这样放大选中的view就不会被遮住。

图片 3原理

a. 改变子View的绘制次序

 /** * 获取子控件dispatchDraw的次序,将当前选中的View放在最后绘制 */ @Override protected int getChildDrawingOrder(int childCount, int i) { //currentItemIndex 为当前选中的View在ViewGroup中的position if (currentItemIndex < 0) { return i; } if (i < (childCount - 1)) { if (currentItemIndex == i) i = childCount - 1; } else { if (currentItemIndex < childCount) i = currentItemIndex; } return i; }

b. 测量子View大小

 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式 */ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); /** * 先测量整个Viewgroup的大小 */ setMeasuredDimension(sizeWidth, sizeHeight); int childCount = getChildCount(); int childWidth, childHeight; /**由于每一个子View的宽高都是一样的所以就一起计算每一个View的宽高*/ if(ShapeType.VERTICAL.ordinal() == mShapeType){ //竖向模式 childWidth = getMeasuredWidth() - padding*2; childHeight = getMeasuredHeight() - padding*2 - edge*2; }else{ //横向模式 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; // 系统自动测量子View:// measureChild(childView, widthMeasureSpec, heightMeasureSpec); /** 以一个精确值来测量子View的宽度 */ childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY); childView.measure(childWidthMeasureSpec,childHeightMeasureSpec); } }

c. 测量子View位置

位置确定最基本原理:

图片 4onlayout

@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; //四个方向的margin值 int measureL = 0, measurelT = 0, measurelR = 0, measurelB = 0; if(ShapeType.VERTICAL.ordinal() == mShapeType){ //竖向模式 switch { 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{ //横向模式 switch { 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; } } } }

d. 手势交互逻辑

在手指滑动的时候为了防止频繁触发翻页,我使用了handler去发送翻页消息。

 /** * 事件分发 * onTouchEvent() 用于处理事件,返回值决定当前控件是否消费了这个事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { Log.d("danxx", "onTouchEvent");// return super.onTouchEvent; /**以屏幕左上角为坐标原点计算的Y轴坐标**/ int y; if(ShapeType.VERTICAL.ordinal() == mShapeType){ //竖屏模式取Y轴坐标 y =  event.getRawY(); }else{ y =  event.getRawX(); //横屏模式取X轴坐标 } switch (event.getAction { case MotionEvent.ACTION_DOWN: Log.i(TAG, "MotionEvent.ACTION_DOWN"); // 手指按下时记录下y坐标 lastY = y; break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "MotionEvent.ACTION_MOVE"); // 手指向下滑动时 y坐标 = 屏幕左上角为坐标原点计算的Y轴坐标 - 手指滑动的Y轴坐标 int m = y - lastY; if(m>0 && m>changeDistance){ //手指向下滑动 或者是左滑 changeHandler.removeMessages; changeHandler.sendEmptyMessageDelayed(MSG_UP, animDuration); }else if(m< 0&& Math.abs>changeDistance){ //手指向上滑动 或者右滑 changeHandler.removeMessages; changeHandler.sendEmptyMessageDelayed(MSG_DOWN, animDuration); } // 记录下此刻y坐标 this.lastY = y; break; case MotionEvent.ACTION_UP: Log.i(TAG, "MotionEvent.ACTION_UP"); break; } return true; }

d. 上下或者左右翻页代码

 /** * 显示下面的一页 * 翻页成功返回true,否则false */ private boolean downPage(){ if(1 == currentItemIndex){ FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration); // 重绘,改变堆叠顺序 currentItemIndex = 2; postInvalidate(); FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, animDuration); return true; }else if(0 == currentItemIndex){ FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration); // 重绘,改变堆叠顺序 currentItemIndex = 1; postInvalidate(); FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, animDuration); return true; }else if(2 == currentItemIndex){ return false; } return false; } /** * 显示上面的一页 * 翻页成功返回true,否则false */ private boolean upPage(){ if(1 == currentItemIndex){ FocusAnimUtils.animItem(getChildAt(currentItemIndex), false, 1.0f, animDuration); // 重绘,改变堆叠顺序 currentItemIndex = 0; postInvalidate(); FocusAnimUtils.animItem(getChildAt(currentItemIndex), true, 1.06f, 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.06f, animDuration); return true; } return false; }

<?xml version="1.0" encoding="utf-8"?><danxx.library.widget.StackCardContainer xmlns:andro xmlns:app="http://schemas.android.com/apk/res-auto" android: 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: 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="血战钢锯岭" android:padding="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: 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:text="你的名字" android:padding="6dp" 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: 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:text="从你的全世界路过" android:lines="1" android:padding="6dp" android:textSize="22sp" android:gravity="center" android:layout_gravity="bottom" android:background="#4EC9AD"/> </android.support.v7.widget.CardView></danxx.library.widget.StackCardContainer>

其实就是在我们自定义的StackCardContainer容器中放置了三个CardView,至于点击事件和数据绑定等完全由用户自己去设置和绑定。StackCardContainer自定义控件只是改变了子View的布局方式并处理手势交互罢了。

本文由365bet亚洲版登录发布于计算机网络,转载请注明出处:用ViewGroup创设一个3D堆集卡牌容器

您可能还会对下面的文章感兴趣: