您的位置:首页 > 其它

自定义类似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大神的一句话,写轮子才能真正学到东西,光会用轮子永远体会不到轮子的精髓,飨你~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: