您的位置:首页 > 移动开发 > Android开发

android自定义粘性控件,综合使用measure,layout,onTouchEvent,onInterceptTouchEvent等方法

2016-03-30 15:37 561 查看

粘性控件

产生粘性控件很简单,我们只需要进行相关的measure和layout并进行事件拦截,和事件处理即可

显示measure方法,measure方法比较复杂,一般我们会根据父容器的测量算子和自身的layoutParams共同决定,这样为了方便起见,全都是让父容器的测量算子决定的

测量代码

`
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
int height = MeasureSpec.getSize(heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
}
`


测量的时候先确定自身的大小,之后再确定view的大小(当然也可以先确定view的大小,之后再确定自己的大小,适用于包裹内容的)

setMeasuredDimension(measureWidth(widthMeasureSpec),

measureHeight(heightMeasureSpec));

这样代码是确定自身的大小。我们直接使用父容器传递过来的参数进行相关高度和宽度的确定

`private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int result = 0;
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 400;
Context ctx = getContext();
if (ctx instanceof Activity) {
Activity a = (Activity) ctx;
result = a.getWindowManager().getDefaultDisplay().getWidth();
}
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
Log.e("", size + "");
}
}
return result;
}`


上面代码是通过测量算子的mode和size,共同确定width

高度也类似

`   private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int result = 0;
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 400;
int count = getChildCount();
if (count > 0) {
View view = getChildAt(0);
view.measure(0, MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.EXACTLY));
result = view.getMeasuredHeight();
}
if (mode == MeasureSpec.AT_MOST) {
// 当为warp_content的时候 给一个最小的默认值
result = Math.min(result, size);
}
}
return result;
}`


为什么我们需要确定我们自身的宽度和高度呢?是因为,如果我们不修改测量算子的话,那么测量的模式是MEASURE_ATMOST,size是父容器给我们传递的宽度和高度,因此,我们想手动的修改的话,就需要修改测量算子值(mode和size)

下面代码是测量孩子的

`@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// TODO Auto-generated method stub
// super.measureChild(child, parentWidthMeasureSpec,
// parentHeightMeasureSpec);
int height = MeasureSpec.getSize(parentHeightMeasureSpec);
child.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT,
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY));
}`


孩子的测量算子宽度使用的是matchParent和EXACTLY。高度我们使用的是父容器的高度,也就是我们粘性控件的高度,mode也是EXACTLY

自身的高度和孩子的高度都测量完了,剩下的我们就是layout了

`protected void onLayout(boolean changed, int l, int t, int r, int b) {
// zhihou zaizheli xunhuan diaoyong childde layout
int count = getChildCount();

for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.layout(0, i * child.getMeasuredHeight(), getMeasuredWidth(),
(i + 1) * child.getMeasuredHeight());

}
totalHeight = (getChildCount() - 1) * getMeasuredHeight();
}`


代码很简单,就是遍历孩子,调用layout方法

接下来是事件拦截

`@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
y = (int) ev.getRawY();
return true;
}
return super.onInterceptTouchEvent(ev);
}`


这里拦截了所有的move事件

我们在ontouchEvent中处理

`public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int min = (int) (event.getRawY() - y);
scrollTo(0, Math.min(totalHeight, Math.max(0, getScrollY() - min)));
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
scroller.startScroll(0, getScrollY(), 0, getPosition()
* getMeasuredHeight() - getScrollY(), Math
.abs(getPosition() * getMeasuredHeight() - getScrollY()));
postInvalidate();
break;
}
return true;
}


`

我们使用的是Scroller完成的滚动,因此需要重写computeScroll

`   @Override
public void computeScroll() {
// TODO Auto-generated method stub
super.computeScroll();
if (scroller.computeScrollOffset()) {
scrollTo(0, scroller.getCurrY());
postInvalidate();
}
}`


接下来,我们计算我们当前显示的是第几个view

`private int getPosition() {
return (int) (getScrollY() / (getMeasuredHeight() + 0.0f) + 0.5f);
}`


在布局加载完的时候给我们的控件添加点击监听

`@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setOnClickListener(this);
}
}`


对外面提供我们自己定义的监听接口

`public interface OnClickItemListener {
public void OnClickItem(View view);
}

public void setOnClickItemListener(OnClickItemListener listener) {
this.listener = listener;
}`


在onClick的时候做出相应

` @Override
public void onClick(View v) {
int position = getPosition();
View child = getChildAt(position);
if (listener != null) {
listener.OnClickItem(child);
}
}


`

源码如下

`package com.example.viewgroup1;

import android.app.Activity;
import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.view.View.OnClickListener;

public class MyViewGroup extends ViewGroup implements OnClickListener {

private int y;
private int totalHeight;
private Scroller scroller;
private OnClickItemListener listener;

public MyViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
scroller = new Scroller(getContext());
}

public MyViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}

public MyViewGroup(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
int height = MeasureSpec.getSize(heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
}

private int measureHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int result = 0;
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 400;
int count = getChildCount();
if (count > 0) {
View view = getChildAt(0);
view.measure(0, MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.EXACTLY));
result = view.getMeasuredHeight();
}
if (mode == MeasureSpec.AT_MOST) {
// 当为warp_content的时候 给一个最小的默认值
result = Math.min(result, size);
}
}
return result;
}

private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int result = 0;
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 400;
Context ctx = getContext();
if (ctx instanceof Activity) {
Activity a = (Activity) ctx;
result = a.getWindowManager().getDefaultDisplay().getWidth();
}
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
Log.e("", size + "");
}
}
return result;
}

@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// TODO Auto-generated method stub
// super.measureChild(child, parentWidthMeasureSpec,
// parentHeightMeasureSpec);
int height = MeasureSpec.getSize(parentHeightMeasureSpec);
child.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT,
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY));
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// zhihou zaizheli xunhuan diaoyong childde layout
int count = getChildCount();

for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.layout(0, i * child.getMeasuredHeight(), getMeasuredWidth(),
(i + 1) * child.getMeasuredHeight());

}
totalHeight = (getChildCount() - 1) * getMeasuredHeight();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int min = (int) (event.getRawY() - y);
scrollTo(0, Math.min(totalHeight, Math.max(0, getScrollY() - min)));
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
scroller.startScroll(0, getScrollY(), 0, getPosition()
* getMeasuredHeight() - getScrollY(), Math
.abs(getPosition() * getMeasuredHeight() - getScrollY()));
postInvalidate();
break;
}
return true;
}

private int getPosition() {
return (int) (getScrollY() / (getMeasuredHeight() + 0.0f) + 0.5f);
}

@Override
public void computeScroll() {
// TODO Auto-generated method stub
super.computeScroll();
if (scroller.computeScrollOffset()) {
scrollTo(0, scroller.getCurrY());
postInvalidate();
}
}

// 点击item的监听
public interface OnClickItemListener {
public void OnClickItem(View view);
}

public void setOnClickItemListener(OnClickItemListener listener) {
this.listener = listener;
}

@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setOnClickListener(this);
}
}

@Override
public void onClick(View v) {
int position = getPosition();
View child = getChildAt(position);
if (listener != null) {
listener.OnClickItem(child);
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
y = (int) ev.getRawY();
return true;
}
return super.onInterceptTouchEvent(ev);
}

}


`

源码下载 http://download.csdn.net/detail/u013356254/9476805

总结,写自定义view的时候比较复杂的是测量和事件冲突

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: