自定义类似ViewPager的效果的ViewGroup
2015-09-14 16:14
351 查看
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
不知为什么下面的字格式去不掉,不管了……
最近换工作,闲下来继续深入研究自定义View,View之后的另一个大类就是ViewGroup,个人理解其为View的容器,应该是一个组合模式的关系,因为ViewGroup也继承自View嘛,ViewGroup要处理几个重要的东西:
1. MeasureSpec的取得和计算;
2. layout摆放子View的位置;
3.处理和子View的滑动冲突等问题
4.更好的滑动体验(主要是Scroller)
想了半天,也参考了一些blog和书籍,觉得ViewPager是个不错的例子,里面塞个list View,还能遇到滑动冲突的问题,在左右滑动的时候还要通过弹性滑动进行页面的跳转,layout和measure倒不是很难,直接上代码,然后分析吧~
package com.amuro.utils.custom_view; import com.amuro.utils.MyUtils; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; public class HScrollView3 extends ViewGroup { private static int VELOCITY_THRESHOLD = 5000; private int lastInterceptX; private int lastInterceptY; private int lastX; private int lastY; private Scroller scroller; private int screenWidth; private VelocityTracker velocityTracker; public HScrollView3(Context context) { this(context, null); } public HScrollView3(Context context, AttributeSet attrs) { this(context, attrs, 0); } public HScrollView3(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); scroller = new Scroller(context); screenWidth = MyUtils.getScreenMetrics(context).widthPixels; velocityTracker = VelocityTracker.obtain(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec); int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec); int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec); int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); if (widthMeasureMode == MeasureSpec.AT_MOST && heightMeasureMode == MeasureSpec.AT_MOST) { setMeasuredDimension(getChildrenHeight(), getChildrenHeight()); } else if (widthMeasureMode == MeasureSpec.AT_MOST) { setMeasuredDimension(getChildrenWidth(), heightMeasureSize); } else if (heightMeasureMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthMeasureSize, getChildrenHeight()); } else { setMeasuredDimension(widthMeasureSize, heightMeasureSize); } } private int getChildrenWidth() { int childCount = getChildCount(); if (childCount == 0) { return 0; } int childrenWidth = 0; for (int i = 0; i < childCount; i++) { childrenWidth += getChildAt(i).getMeasuredWidth(); } return childrenWidth; } private int getChildrenHeight() { int childCount = getChildCount(); if (childCount == 0) { return 0; } return getChildAt(0).getMeasuredHeight(); } /** * getMeasuredWidth才能得到宽度 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { int childCount = getChildCount(); int left = 0; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child.getVisibility() == View.VISIBLE) { int childWidth = child.getMeasuredWidth(); child.layout(left, 0, left + childWidth, child.getMeasuredHeight()); left += childWidth; } } } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int action = ev.getAction(); int nowX = (int) ev.getX(); int nowY = (int) ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: intercepted = false; if (!scroller.isFinished()) { scroller.abortAnimation(); intercepted = true; } break; case MotionEvent.ACTION_MOVE: int dx = nowX - lastInterceptX; int dy = nowY - lastInterceptY; if(Math.abs(dx) > Math.abs(dy)) { intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; } lastInterceptX = nowX; lastInterceptY = nowY; lastX = nowX; lastY = nowY; return intercepted; } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent ev) { velocityTracker.addMovement(ev); int action = ev.getAction(); int nowX = (int) ev.getX(); int nowY = (int) ev.getY(); switch (action) { case MotionEvent.ACTION_DOWN: if (!scroller.isFinished()) { scroller.abortAnimation(); } break; case MotionEvent.ACTION_MOVE: int dx = nowX - lastX; scrollBy(-dx, 0); break; case MotionEvent.ACTION_UP: int index = getScrollX() / screenWidth + 1; int count = getChildCount(); int xToScroll = 0; velocityTracker.computeCurrentVelocity(1000); float xVelocity = velocityTracker.getXVelocity(); //< 0 hand to Left if (xVelocity < 0) { if (index == count) { xToScroll = -(screenWidth - (index * screenWidth - getScrollX())); } else { if (Math.abs(xVelocity) > VELOCITY_THRESHOLD) { xToScroll = index * screenWidth - getScrollX(); } else { xToScroll = -(screenWidth - (index * screenWidth - getScrollX())); } } } else { if (getScrollX() < 0) { xToScroll = -getScrollX(); } else { if (Math.abs(xVelocity) > VELOCITY_THRESHOLD) { xToScroll = -(screenWidth - (index * screenWidth - getScrollX())); } else { xToScroll = index * screenWidth - getScrollX(); } } } Log.e("Amuro", "Index -> " + index); Log.e("Amuro", "Velocity -> " + xVelocity); Log.e("Amuro", "ScrollX -> " + getScrollX()); Log.e("Amuro", "dx -> " + xToScroll); scroller.startScroll(getScrollX(), 0, xToScroll, 0); invalidate(); velocityTracker.clear(); break; } lastX = nowX; lastY = nowY; return true; } @Override public void computeScroll() { if(scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { velocityTracker.recycle(); super.onDetachedFromWindow(); } }
大概分析一下(不要吐槽我的类名,因为我改了三个版本改成这个基本完成的版本,心好累):
1. onMeasure的时候主要还是处理wrap_content时候的宽度和高度,因为这是一个容器,所以他的大小如果不特别指定的话,都是靠子View来设定的,所以上来先调用measureChildren方法,后面才能获得子View的宽度或高度;
2. layout方法不需要多说,left,top,right,bottom设定好调子View的layout方法就行了,注意获得子View的宽度依然是调用getMeasuredWidth而不是getWidth;
3. onInterceptedTouchEvent方法里处理掉左右滑动事件,否则根据安卓的滑动事件传递原理,左右滑动事件将被子View吸收导致我们的ViewGroup收不到左右滑动事件,判断方式很简单,左右滑动的距离大于上下滑动的距离就行了。
4. 好了,当拦截了左右滑动事件后,在onTouchEvent事件中处理掉就行了,调用scrollBy就行了,这个很简单。难点在后面ACTION_UP的时候,我们需要通过弹性滑动让页面滑到用户想要的index去,这里最好的办法是用纸笔画图来计算scroller需要滑动的距离,比较复杂,各位可以自己算,方法不是唯一的。其中还用到了加速度监测的类VelocityTracker,这个类很好用,基本都是那个流程调用。实现的效果就是用户滑动很快超过某个阈值的时候(这里是5000)认为用户在切换page,否则还是弹性滑动到当前页面。
好了,写个Activity测试一下下:
package com.amuro.main; import java.util.ArrayList; import com.amuro.chapter3test.R; import com.amuro.utils.MyUtils; import com.amuro.utils.custom_view.HScrollView3; import android.annotation.SuppressLint; import android.app.Activity; import android.os.Bundle; import android.view.ViewGroup.LayoutParams; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity3 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_3_layout); initView(); } private void initView() { HScrollView3 hsv = (HScrollView3) findViewById(R.id.st); for (int i = 0; i < 5; i++) { hsv.addView(createListView(i)); } } @SuppressLint("InlinedApi") private ListView createListView(int index) { ListView listView = new ListView(this); listView.setLayoutParams(new LayoutParams(MyUtils .getScreenMetrics(this).widthPixels, LayoutParams.MATCH_PARENT)); ArrayList<String> datas = new ArrayList<>(); for (int i = 0; i < 50; i++) { datas.add("Page " + index + ", item " + i); } ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.activity_list_item, android.R.id.text1, datas); listView.setAdapter(adapter); return listView; } }
顺便还写了个自动生成ListView的方法,哈哈,基本完工,后面有时间再写一个竖着玩的~
有人问写这个玩意儿有啥用,借用simple大神的一句话,写轮子才能真正学到东西,光会用轮子永远体会不到轮子的精髓,飨你~
相关文章推荐
- 贪心算法
- Bootstrap轮播
- 字节对齐
- iOS 开发之 导航控制器
- 基因数据压缩算法(ACTG) C++
- 低效率分析……
- Windows10怎么把两张图片合并成一张图片 Windows10把两张图片合并成一张图片方法
- 好的文章
- cookie 实现跨域
- 黑马程序员学习(一) JAVA概述和环境搭建
- nginx 安装阶段整个项目的配置文件分析
- css3--display:table-cell
- java学习值Character类
- view odoo rst
- WebService学习笔记(六)Spring与CXF整合服务端
- 使用css灰化图片
- Bash 中的环境变量
- JENKINS的远程API调用,然后用PYTHON解析出最新的版本及稳定成功的版本
- SSIS Package或Task的MaximumErrorCount属性
- 2015-9-14 项目1- 顺序表的基本运算