自定义View系列
1.View是什么
View是Android所有控件的父类,其子类包括ViewGroup和View
2.View的基础知识
2.1 View的位置参数
Button button = new Button(this);
float x = button.getX(); // 相对父容器的左上角坐标 x = left + granslationX
float y = button.getY(); // 相对父容器的左上角坐标 y = left + granslationY
int left = button.getLeft(); //相对父容器的左上角横坐标
int top = button.getTop(); //相对父容器的左上角纵坐标
int right = button.getRight();//相对父容器的右下角横坐标
int bottom = button.getBottom();//相对父容器的右下角纵坐标
float translationX = button.getTranslationX();//相对父容器的偏移量横坐标
float translationY = button.getTranslationY();//相对父容器的偏移量纵坐标
int height = button.getHeight(); // 高度,height = bottom - top
int width = button.getWidth(); // 宽度,width = right - left
2.2MotionEvent
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: // 点击那瞬间
float rawX = ev.getRawX(); // 相对屏幕的横坐标
float rawY = ev.getRawY();// 相对屏幕的纵坐标
float x = ev.getX();// 相对父容器的横坐标
float y = ev.getY();// 相对父容器的纵坐标
break;
case MotionEvent.ACTION_MOVE: // 移动
break;
case MotionEvent.ACTION_UP: //松开
break;
}
return true;
}
2.3 TouchSlop
int scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
touchSlop为判断系统最小的滑动距离,大于它意味着滑动,不大于它意味着不滑动
2.4 VelocityTracker
@Override
public boolean onTouchEvent(MotionEvent ev) {
VelocityTracker veloctiyTracker = VelocityTracker.obtain();
veloctiyTracker.addMovement(ev);
veloctiyTracker.computeCurrentVelocity(1000); // 单位时间内测试速度,单位是ms
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float xVelocity = veloctiyTracker.getXVelocity(); // 滑动在单位时间内的速度,可以是负数
float yVelocity = veloctiyTracker.getYVelocity();
break;
case MotionEvent.ACTION_UP:
veloctiyTracker.clear();
veloctiyTracker.recycle(); //不用时候重置回收
break;
}
return true;
}
2.5 GestureDetector
@Override
public boolean onTouchEvent(MotionEvent ev) {
GestureDetector gestureDetector = new GestureDetector(getContext(),new MyGestureDetector());
gestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
gestureDetector.onTouchEvent(ev);
return true;
}
public class MyGestureDetector implements GestureDetector.OnGestureListener{
@Override
public boolean onDown(MotionEvent e) { //触发瞬间
return false;
}
@Override
public void onShowPress(MotionEvent e) { //触发瞬间后,停在那里,没有滑动操作
}
@Override
public boolean onSingleTapUp(MotionEvent e) { //单击
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false; // 由Action Down 和连续的Action Move完成
}
@Override
public void onLongPress(MotionEvent e) { // 长按
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false; // 快速滑动
}
}
class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener{
@Override
public boolean onSingleTapConfirmed(MotionEvent e) { //严格的单击行为,和onSingleTapUp不同
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) { // 双击
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) { // 双击行为,Action Down,Move,Up都会触发该回调
return false;
}
}
3.View的滑动
3.1 使用scrollTo/scrollBy
scrollTo和scrolBy只能改变View内容的位置,不能改变其本身的位置,看源码
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
scrollBy本质是调用了scrollTo,mScrollX为View的左边缘和内容的左边缘的距离,mScrollY为View的下边缘和内容的下边缘的距离,如果scrollTo(x,y),x为正,则View内容向负坐标偏移,为负,则Viw内容向正坐标偏移,和我们一般偏移常识刚好相反
3.2 使用动画
传统的View动画只能改变内容位置,不能改变真正的View位置,属性动画才能改变View真正的位置,动画说明见动画方面文章
3.3 改变布局参数
使用View的LayoutParams属性动态改变
3.4 使用Scroller(平滑)
Scroller的模版使用
public void startScroll() {
scroller = new Scroller(context);
scroller.startScroll(100,100,-300,-300,2000);
invalidate();
}
@Override
public void computeScroll() {
if (scroller!=null && scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(),scroller.getCurrY());
postInvalidate();
}
}
Scroller调用startScroll开始滑动,看startScroll方法
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
没有滑动开始的逻辑,这是因为什么呢?其实现实质是调用了invalidate重绘,在draw方法中,调用了computeScroll方法,在draw方法中的computeScroll是空实现,所以我们需要重写方法,在computeScroll重写,调用scrollTo获取位置后滑动,而为什么Scroller能够实现顺滑,而不像scrollTo只能瞬滑,看computeScrollOffset的源码
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}
mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
}
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
通过时间百分比的变化,实时的算出mCurrX和mCurrY的位置,然后用scrollTo方法变更位置,后再调用了postInvalidate继续重绘,最后实现了平滑的操作,不过要注意,它只能对View的内容操作!!!