一、View的基础知识
1)view的位置参数
1.view的位置主要有四个属性:top.left.right.bottom;(是相对父控件来说的,及相对坐标)。android 3.0增加x,y,(view左上角的坐标)translationX,translationY(左上角相对于父容器的偏移量);及x = left + translationX, y = top + translationY;
2.在平移的时候,top和left还是原始左上角的位置信息,发生改变的是x, y, translationX, translationY。
2)MotionEvent 和 TouchSlop
1.典型的事件有:
ACTION_DOWN-----手指刚接触屏幕;
ACTION_MOVE-----手指在屏幕上移动;
ACTION_UP-----松开是一瞬间;
2.由MotionEvent对象我们可以获取发生的x和y坐标;系统提供了 getX/getY(返回的是相对于当前的view左上角的坐标),getRawX/getRawY(返回的是相对于手机屏幕左上角的坐标)。
3.TouchSlop是系统所能识别出的被认为是滑动的最小距离。通过ViewConfiguration.get(getContext()).getScaledTouchSlop()获取该常量;
3)VelocityTracker,GestureDetector,Scroller
1.velcityTracker用于追踪手指在滑动中的速度(包括水平和竖直方向的速度)。
首先在view的onTouchEvent中追踪当前单击事件的速度及:
VelocityTracker velocityTracker = VelocityTracker.obtain( );
velocityTracker.addmMovement(event);
获取当前速度:
//在获取速度之前,必须先计算速度
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = velocityTracker.getXVelocity( );
int yVelocity = velocityTracker.getYVelocity( );
当不需要的时候,调用clear来重置并回收内存:
velocityTracker.clear(); velocityTracker.recycle();
2.GestureDetector:手势检测,用于检测用户的单机.滑动.长按.双击等行为.
3.Scroller:用于实现View的弹性滑动;需要和computeScroll配合使用才能共同实现弹性滑动。
二.View 的滑动
1)使用scrollTo/scrollBy,scrollBy实际上也是调用了scrollTo方法。
2).使用动画
主要是操作view的translationX和translationY属性来进行平移的。当有单机事件,由于平移了位置,使得当前位置的控件响应不了单机事件,原始位置能响应该事件。属性动画可以解决该问题。android 3.0以下的版本要用其他方法处理。
3).改变布局参数 及改变Layoutparams。
三种滑动的比较:
1.scrollTo/scrollBy:操作简单,适合对view内容的滑动;
2.动画:操作简单,主要使用于没有交互的view和实现复杂的动画效果;
3.改变布局参数:操作稍微复杂,适用于有交互的view。
三.view的事件分发机制
点击事件的分发过程由三个很主要的方法共同实现:
dispatchTouchEvent(MotionEvent ev) :用于进行事件的分发。返回结果受当前view的onTouchEvent()和下级view的dispatchTouchEvent方法的影响;
onInterceptTouchEvent(MotionEvent event)在上述方法内部调用;返回是否拦截当前事件;
onTouchEvent(MotionEvent event) 在dispathTouchEvent方法中调用,用来处理点击事件。
三者的关系:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
四.View的滑动冲突
存在如下的滑动冲突的情况;
1)外部滑动方向和内部滑动方向不一致;
2)外部滑动方向和内部滑动方向一致;
3)如上二者的嵌套;
解决滑动冲突
1)外部拦截法
点击事件都先经过父容器的拦截处理;重写父容器的onInterceptTouchEvent方法。
2)内部拦截法
点击事件都传递给子元素,子元素重写dispatchTouchEvent方法;需要配合requestDisallowInterceptTouchEvent方法才能正常工作。较为复杂,推荐使用外部拦截法。
实例(使用外部拦截法来处理第一种情况)
自定义一个view如下:
package com.example.horizontalscrollviewex;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
public class HorizontalScrollViewEx extends ViewGroup {
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
// 分别记录上次滑动的坐标
private int mLastX = 0;
private int mLastY = 0;
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
private int mChildWidth;
private int mChildrenSize;
private int mChildIndex;
public HorizontalScrollViewEx(Context context) {
this(context, null);
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int childCount = getChildCount();
mChildrenSize = childCount;
for (int i = 0; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() == View.GONE) {
final int childWidth = childView.getMeasuredWidth();
mChildrenSize = childWidth;
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}
private void init(){
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
final int childCount = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
if (childCount == 0) {
setMeasuredDimension(0, 0);
}else if (widthSpaceMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
setMeasuredDimension(measuredWidth, heightSpaceSize);
} else if (heightSpaceMode == MeasureSpec.AT_MOST) {
final View childView = getChildAt(0);
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(widthSpaceSize, measuredHeight);
}else {
final View childView = getChildAt(0);
measuredWidth = childView.getMeasuredWidth() * childCount;
measuredHeight = childView.getMeasuredHeight();
setMeasuredDimension(measuredWidth, measuredHeight);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
int scrollX = getScrollX();
int scrollToChildIndex = scrollX / mChildWidth;
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
int dx = mChildIndex * mChildWidth - scrollX;
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
}
default:
break; }
mLastX = x;
mLastY = y;
return true;
}
private void smoothScrollBy(int dx, int i) {
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}
主程序:
package com.example.horizontalscrollviewex;
import java.util.ArrayList;
import com.example.horizontalscrollviewex.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private HorizontalScrollViewEx mListContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
LayoutInflater inflater = getLayoutInflater();
mListContainer = (HorizontalScrollViewEx) findViewById(R.id.container);
final int screenWidth = getScreenMetrics(this).widthPixels;
final int screenHeight = getScreenMetrics(this).heightPixels;
for (int i = 0; i < 3; i++) {
ViewGroup layout = (ViewGroup) inflater.inflate(
R.layout.content_layout, mListContainer, false);
layout.getLayoutParams().width = screenWidth;
TextView textView = (TextView) layout.findViewById(R.id.title);
textView.setText("page " + (i + 1));
layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0));
createList(layout);
mListContainer.addView(layout);
}
}
private void createList(ViewGroup layout) {
ListView listView = (ListView) layout.findViewById(R.id.list);
ArrayList<String> datas = new ArrayList<String>();
for (int i = 0; i < 50; i++) {
datas.add("name " + i);
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.content_list_item, 1, datas);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MainActivity.this, "click item",
Toast.LENGTH_SHORT).show();
}
});
}
public static DisplayMetrics getScreenMetrics(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm;
}
}
源码:https://github.com/yang-mr/manage (HorizontalScrollViewEx)