一、View的滑动:
1.获取最小滑动距离:
int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
2.Scroller类(滑动控制):
(1)创建:
Scroller scroller = new Scroller(context);
(2)控制滑动:
//getScrollX()获取当前view的x坐标,
mScroller.startScroll(getScrollX(), 0, 距离值, 0, Math.abs(delta)); //此句设置目标坐标值
invalidate(); //此句真实移动
(3)实现computeScroll方法,进行实时移动(多次移动就形成了连续动画):
@Override
public void computeScroll() { //此方法会一直被调用,直到移到目标位置
if (scroller.computeScrollOffset()) {
//使用scrollTo移到目标位置,传入目标位置xy坐标;也可以使用scrollBy,scrollBy传入从原位置到目标位置的距离值
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate(); //此句真实移动,会让View重绘,computeScroll方法一直被调用
}
}
3.VelocityTracker类(获取x轴与y轴滑动速率,单位:像素/时间):
(1)创建:
VelocityTracker vt = VelocityTracker.obtain();
(2)接管View的onTouchEvent事件:
vt.addMovement(event);
(3)计算速率:
vt.computeCurrentVelocity(1000);
(4)获取速率:
float xV = vt.getXVelocity(); //x轴方向速率
float yV = vt.getYVelocity(); //y轴方向速率
(5)释放:
vt.clear();
vt.recycle();
4.GestureDetector类(对一些手势的封装):
二、父View与子View嵌套滑动处理:
GestureDetector gd = new GestureDetector(new GestureDetector.OnGestureListener(){
@Override
public boolean onDown(MotionEvent e) { //按下(ACTION_DOWN)
return false;
}
@Override
public void onShowPress(MotionEvent e) {//按下未松开或拖动(ACTION_DOWN)
}
@Override
public boolean onSingleTapUp(MotionEvent e) {//松开(ACTION_UP)
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //按下并拖动(ACTION_MOVE)
return false;
}
@Override
public void onLongPress(MotionEvent e) { //长按
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //快速滑动,velocityX为x轴的速率,velocityY为y轴的速率
return false;
}
});
gd.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener(){
@Override
public boolean onSingleTapConfirmed(MotionEvent e) { //单击
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) { //双击
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) { //表示发生了双击事件
return false;
}
});
//处理长按后无法拖动的问题
gd.isLongpressEnabled();
1.父View的onInterceptTouchEvent中处理ACTION_MOVE事件,对需要由父View拖动的情况返回true:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
if (条件) { //需要由父View处理的条件
return true; //由父View处理事件
}
break;
}
return false;
}
2.由子View处理事件:
(1)父View的onInterceptTouchEvent中除ACTION_DOWN事件全部返回true:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
return false;
}
return true;
}
(2)子View的dispatchTouchEvent方法中处理
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//让父view不拦截事件
parentView.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (条件) { //父View需要处理事件的条件
//让父view拦截事件
parentView.requestDisallowInterceptTouchEvent(false);
}
break;
}
return false;
}
三、View的测量模式:
SpecMode模式有3种:
UNSPECIFIED:
无大小限制,此值一般在系统内使用
EXACTLY:
精确大小,指定match_params和或数值
AT_MOST:
大小不确定,对应wrap_content
获取SpecMode模式:
int specMode = MeasureSpec.getMode(spce);
获取宽高值
int specSize = MeasureSpec.getSize(spce);
四、自定义View:
1.继承View,重写onDraw方法,显示内容,并且处理有padding值的情况:
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
int leftPd = getPaddingLeft();
int rightPd = getPaddingRight();
int topPd = getPaddingTop();
int bottomPd = getPaddingBottom();
int width = getWidth() - leftPd - rightPd; //实际宽度需要减去left与right的padding值
int height = getHeight() - topPd - bottomPd; //实际高度需要减去top与bottom的padding值
//绘制内容,比如canvas.drawLine()
...
}
2.重写onMeasure,处理宽度与高度为wrap_content的情况:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //获取宽度SpecMode模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //获取宽度Size大小
int heightMode = MeasureSpec.getMode(heightMeasureSpec); //获取高度SpecMode模式
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //获取高度Size大小
/*
* 处理View使用wrap_content的情况,给个默认宽度与高度值,不处理时wrap_content和match_parent效果一样
*/
if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){ //宽高都为wrap_content
setMeasuredDimension(自定义大小, 自定义大小);
} else if(widthMode == MeasureSpec.AT_MOST){ //宽都为wrap_content
setMeasuredDimension(自定义大小, heightSize);
} else if(heightMode == MeasureSpec.AT_MOST){ //高都为wrap_content
setMeasuredDimension(widthSize, 自定义大小);
}
}
四、自定义ViewGroup:
1.重写onMeasure方法,为子View测量宽度与高度,为自身测量大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 内部会循环调用子View的measure,测量子View大小
measureChildren(widthMeasureSpec, heightMeasureSpec);
//以下为自身测量大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec); // 获取宽度SpecMode模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec); // 获取宽度Size大小
int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 获取高度SpecMode模式
int heightSize = MeasureSpec.getSize(heightMeasureSpec); // 获取高度Size大小
int childCount = getChildCount();
View v = null;
if (childCount > 0) {
v = getChildAt(0);
}
if (v != null) {
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { // 宽高都为wrap_content,全用子View的
setMeasuredDimension(v.getMeasuredWidth() * childCount, v.getMeasuredHeight());
} else if (widthMode == MeasureSpec.AT_MOST) { // 宽为wrap_content
setMeasuredDimension(v.getMeasuredWidth() * childCount, heightSize); //宽度为子View之和,高度为原有的值
} else if (heightMode == MeasureSpec.AT_MOST) { // 高为wrap_content
setMeasuredDimension(widthSize, v.getMeasuredHeight()); //宽度用原有的,高度为子View的高度
}
}
}
2.重写onLayout方法,为子View布局位置与大小:
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
int childCount = getChildCount(); //取出子View个数
int left = 0;
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) { //只布局显示的
int widthMeasured = v.getMeasuredWidth();
v.layout(left, 0, left + widthMeasured, v.getMeasuredHeight());
left = left + widthMeasured; //按左右布局子View
}
}
}
五、View的自定义属性:
1.在values下创建attrs.xml文件(文件名可以任意):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="属性集合名">
<attr name="属性名1" format="color" /> //color表示颜色属性(reference为资源id,还有基本数据类型等)
</declare-styleable name="">
</resources>
2.在xml中使用自定义属性:
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto" //此句必须,这里以app为前缀,可以为别的名称
... >
<com...View
app:属性名1="@color/theme_green" //为自定义属性设值
... />
</RelativeLayout>
3.在代码中获取布局中的自定义属性:
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.属性集合名);
int color = ta.getColor(styleable.属性名1, 默认值);
ta.recycle();