自定义类似ViewPager的效果的ViewGroup

<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大神的一句话,写轮子才能真正学到东西,光会用轮子永远体会不到轮子的精髓,飨你~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值