自定义类似ViewPager的效果的ViewGroup

来源:互联网 发布:ubuntu卸载openjdk1.8 编辑:程序博客网 时间:2024/04/19 11:05

<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();}@Overrideprotected 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才能得到宽度 */@Overrideprotected 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;}}}}@Overridepublic 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")@Overridepublic 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;}@Overridepublic void computeScroll(){if(scroller.computeScrollOffset()){scrollTo(scroller.getCurrX(), scroller.getCurrY());postInvalidate();}}@Overrideprotected 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{@Overrideprotected 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大神的一句话,写轮子才能真正学到东西,光会用轮子永远体会不到轮子的精髓,飨你~

1 0
原创粉丝点击