自定义ViewGroup实现HorizontalScrollView的效果
2018-01-21 10:48
441 查看
自定义ViewGroup主要有三个步骤:
1. 实现onLayout进行布局
2. 实现onMeasure测量
3. 支持margin
4. 实现弹性滑动
5. 解决滑动冲突
1. 实现onLayout进行布局
2. 实现onMeasure测量
3. 支持margin
4. 实现弹性滑动
5. 解决滑动冲突
class CustomViewByViewGroup(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int): ViewGroup(context, attrs, defStyleAttr, defStyleRes){ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int):this(context, attrs, defStyleAttr, 0) constructor(context: Context, attrs: AttributeSet):this(context, attrs, 0, 0) constructor(context: Context): this(context, null, 0, 0) var lastX: Float = 0f var lastY: Float = 0f /**=========================================================== * 1. 继承ViewGroup必须实现onLayout方法 *============================================================*/ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { var childLeft = paddingLeft //需要处理padding for(i in 0 until childCount){ val childView = getChildAt(i) if(childView.visibility != View.GONE){ val childWidth = childView.measuredWidth //2. 额外处理margin属性 val childLayoutParams = childView.layoutParams as MarginLayoutParams childLeft += childLayoutParams.leftMargin childView.layout(childLeft, childLayoutParams.topMargin + paddingTop, childLeft + childWidth, childLayoutParams.topMargin + paddingTop + childView.measuredHeight) //一定要根据margin处理好四个顶点坐标 childLeft += childWidth + childLayoutParams.rightMargin } } } /**===================================================================== * 2. 定义ViewGroup的布局测量过程(也需要额外处理margin) *=======================================================================*/ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec) val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec) val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec) val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec) var measureWidth = 0 var measureHeight = 0 //2. 需要测量所有子View! measureChildren(widthMeasureSpec, heightMeasureSpec) //3. 本身宽高的模式均为wrap_content, 需要根据子View来获得 if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){ for(i in 0 until childCount){ val childView = getChildAt(i) measureWidth += childView.measuredWidth //测量出总宽度 //6. 处理marigin val childLayoutParams = childView.layoutParams as MarginLayoutParams measureWidth += childLayoutParams.leftMargin + childLayoutParams.rightMargin val totalCurChildHeight = childView.measuredHeight + childLayoutParams.topMargin + childLayoutParams.bottomMargin if(totalCurChildHeight > measureHeight){ measureHeight = totalCurChildHeight //选取子View中高度最大的 } } //7. 处理padding measureWidth += paddingLeft + paddingRight measureHeight += paddingTop + paddingBottom setMeasuredDimension(measureWidth, measureHeight) } //4. 仅有高度是wrap_content else if(heightSpecMode == MeasureSpec.AT_MOST){ //获取所有子View最大的高度,宽度直接用给定的尺寸 for(i in 0 until childCount){ val childView = getChildAt(i) // 处理高度(wrap_content)上marigin val childLayoutParams = childView.layoutParams as MarginLayoutParams val totalCurChildHeight = childView.measuredHeight + childLayoutParams.topMargin + childLayoutParams.bottomMargin if(totalCurChildHeight > measureHeight){ measureHeight = totalCurChildHeight //选取子View中高度最大的 } } measureHeight += paddingTop + paddingBottom //处理高度的padding setMeasuredDimension(widthSpecSize, measureHeight) } //5. 仅有宽度是wrap_content else if(widthSpecMode == MeasureSpec.AT_MOST){ for(i in 0 until childCount){ val childView = getChildAt(i) measureWidth += childView.measuredWidth // 处理宽度(wrap_content)上marigin val childLayoutParams = childView.layoutParams as MarginLayoutParams measureWidth += childLayoutParams.leftMargin + childLayoutParams.rightMargin } measureWidth += paddingLeft + paddingRight // 处理宽度的padding setMeasuredDimension(measureWidth, heightSpecSize)//高度直接用给定的尺寸 } } /**=============================================================== * 1. 要支持Margin功能,必须要重写方法,并实现自己LayoutParams *=================================================================*/ override fun generateDefaultLayoutParams() = MyLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) override fun generateLayoutParams(attrs: AttributeSet) = MyLayoutParams(context, attrs) override fun generateLayoutParams(p: LayoutParams): MyLayoutParams{ when(p){ is LayoutParams -> return MyLayoutParams(p) is MarginLayoutParams -> return MyLayoutParams(p) else -> return MyLayoutParams(p) } } open class MyLayoutParams : MarginLayoutParams { constructor(c: Context, attrs: AttributeSet) : super(c, attrs) constructor(width: Int, height: Int) : super(width, height) constructor(p: ViewGroup.LayoutParams) : super(p) {} constructor(source: ViewGroup.MarginLayoutParams) : super(source) } /**============================================================== * 3. 滑动所需的scroller *==============================================================*/ val mScroller: Scroller init { mScroller = Scroller(context) } override fun computeScroll() { if(mScroller.computeScrollOffset()){ scrollTo(mScroller.currX, mScroller.currY) invalidate() } } fun smoothScrollTo(destX: Int, destY: Int){ mScroller.startScroll(scrollX, scrollY, destX - scrollX, destY - scrollY, 500) invalidate() } var curChildIndex = 0 /**============================================================== * 4. 左右滑动+页面切换(类似HorizontalScrollView) *==============================================================*/ override fun onTouchEvent(event: MotionEvent): Boolean { val curX = event.x val curY = event.y when(event.action){ //1. View跟随手指滑动 MotionEvent.ACTION_MOVE -> { val deltaX = curX - lastX scrollBy(-deltaX.toInt(), 0) } //2. 滑动一定距离后进行页面弹性切换 MotionEvent.ACTION_UP -> { //3. 滑动的距离需要为 scrollx - 当前View左边所有View的总宽度 = distance var totalLeftChildrenWidth = 0 for(i in 0 until curChildIndex){ // 计算出加上子View测量宽+左右margin=总child长度 val childView = getChildAt(i) val childLayoutParams = childView.layoutParams as MarginLayoutParams totalLeftChildrenWidth += childView.measuredWidth + childLayoutParams.leftMargin + childLayoutParams.rightMargin } val distance = scrollX - totalLeftChildrenWidth //4. 比较距离和当前View的宽度,来判断是否切换页面 val curChildView = getChildAt(curChildIndex) val childLayoutParams = curChildView.layoutParams as MarginLayoutParams val curChildWidth = curChildView.measuredWidth + childLayoutParams.leftMargin + childLayoutParams.rightMargin if(Math.abs(distance) > curChildWidth / 3){ if(distance < 0 && curChildIndex > 0){ //向左滑动(最左面页面不会向左滑动) curChildIndex-- }else if(distance > 0 && (curChildIndex < childCount - 1)){ //向右滑动 curChildIndex++ } } //5. 滑动到当前Index表示的页面(Scroller弹性滑动) var totalLeftChildWidth = 0 for(i in 0 until curChildIndex){ // 计算除了当前View的左侧所有View的总宽度 val childView = getChildAt(i) val childLayoutParams = childView.layoutParams as MarginLayoutParams totalLeftChildWidth += childView.measuredWidth + childLayoutParams.leftMargin + childLayoutParams.rightMargin } smoothScrollTo(totalLeftChildWidth,0) } else -> { } } lastX = curX lastY = curY return true } /**============================================================== * 5. 处理滑动冲突(水平方向的就直接拦截,竖直方向的给子View) *==============================================================*/ var downX = 0f var downY = 0f override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { var isIntercept = super.onInterceptTouchEvent(ev) when(ev.action){ MotionEvent.ACTION_DOWN ->{ downX = ev.x downY = ev.y isIntercept = false } MotionEvent.ACTION_MOVE ->{ val deltaX = ev.x - downX val deltaY = ev.y - downY isIntercept = (Math.abs(deltaX) >= Math.abs(deltaY)) //deltaX大,表示左右滑动,直接返回true } MotionEvent.ACTION_UP ->{ isIntercept = false } } /**=========================================== * 注意: * 1. 页面切换后,lastX/Y还是切换时刻的值, * 2. 拦截后直接跳到onTouchEvent的ACTION_MOVE执行 * 3. 错误的lastX会导致滑动错乱,因此进入MOVE处理前,这里要记录最新的lastX/Y *=============================================*/ lastX = ev.x lastY = ev.y return isIntercept } }
相关文章推荐
- 自定义HorizontalScrollView控件(实现QQ5.0侧滑效果)
- 自定义HorizontalScrollView视图实现仿ViewPager效果
- 自定义导航栏HorizontalScrollView+RadioGroup实现
- 安卓开发笔记——自定义HorizontalScrollView控件(实现QQ5.0侧滑效果)
- Android 自定义View修炼-自定义HorizontalScrollView视图实现仿ViewPager效果
- Android 自定义 HorizontalScrollView 实现ViewPager效果,打造再多图片(控件)也不怕 OOM
- Android 实现横向标题栏滚动效果(HorizontalScrollView + GridView + Viewpager + 自定义适配器)
- HorizontalScrollView下自定义ViewGroup无法实现滑动
- HorizontalScrollView+viewpager实现仿天天动听ios版和QQ侧边菜单效果
- 使用HorizontalScrollView实现侧滑效果(3)
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android ScrollView HorizontalScrollView 实现全方向(上下左右)反弹效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android自定义控件系列五:自定义ViewGroup(一)实现ViewPager效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- 51.使用HorizontalScrollView+LinearLayout实现文字+图片的自动跑马灯效果
- 自定义ViewGroup 实现拖动跟快速滚动的效果
- 自定义ViewGroup实现多个单页面上下滑动效果
- Android自定义ViewGroup实现绚丽的仿支付宝咻一咻雷达脉冲效果