Android自定义控件系列——View与内容作滑动或动画
程序员文章站
2022-05-04 20:01:18
...
View与内容作滑动或动画
控件滑动
View滑动的本质就是随着手指的运动不断地改变坐标。 当触摸事件传到View时,系统记下触摸点的坐标,手指移动时系统记下移动后的触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标,不断的重复这样的过程,从而实现滑动过程
方法一:layout(),控制View的坐标
private int lastX = 0;
private int lastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 在当前left、top、right、bottom的基础上加上偏移量
layout(getLeft() + offsetX,
getTop() + offsetY,
getRight() + offsetX,
getBottom() + offsetY);
//重新设置初始坐标
x = (int)(event.getRawX());
y = (int)(event.getRawY());
break;
}
return true;
}
方法二:offsetLeftAndRight() 与 offsetTopAndroidBottom()
private int lastX = 0;
private int lastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY
//同时对于left 和 right进行偏移
offsetLeftAndRight(offsetX);
//同时对于top 和 bottom进行偏移
offsetTopAndBottom(offsetY);
//重新设置初始坐标
x = (int)(event.getRawX());
y = (int)(event.getRawY());
break;
}
return true;
}
方法三:LayoutParams,改变布局参数
private int lastX = 0;
private int lastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
//获得 LayoutParams对象的时候,需要将其转换成直接父View(这个View的上一层布局)的类型
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
//重新设置初始坐标
x = (int)(event.getRawX());
y = (int)(event.getRawY());
break;
}
return true;
}
方法四:scrollTo()与ScrollBy()
private int lastX = 0;
private int lastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
//scroll方法移动的是View的父布局,因此,子View相对于父布局移动,相当于父布局取反移动
((View)getParent()).scrollBy(-offsetX,-offsetY);
//重新设置初始坐标
x = (int)(event.getRawX());
y = (int)(event.getRawY());
break;
}
return true;
}
方法五:Scroller
//Scroll本身无法让View滑动,它需要和View的computeScroll方法配合才能使用
Scroller scroller =new Scroller(mContext);
private int lastX = 0;
private int lastY = 0;
@Override
public boolean onTouchEvent(MotionEvent event){
int getX = (int) event.getX();
int getY = (int) event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = getX;
lastY = getY;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = getX - lastX;
int offsetY = getY - lastY;
smoothScrollTo(-offsetX, -offsetY);
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
@Override
public void computeScroll(){
// 判断Scroller滑动是否执行完毕
if (mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 通过重绘让系统调用onDraw,onDraw中又会调用computeScroll,如此不断循环,直到Scroller执行完毕
postInvalidate();
}
}
private void smoothScrollTo(int destX, int destY){
int scrollX = getScrollX();
int delta = destX = scrollX;
mScroller.startScroll(scrollX, 0, delta, 0);
// 必须调用该方法通知View重绘以便computeScroll方法被调用
invalidate();
}
方法六:动画,帧动画,属性动画,LayoutAnimation
略
方法七:ViewDragHelper
View内容作动画
方法一:ValueAnimator,结合setInterpolator等属性
//创建一个动画过程
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
//设置过程的持续行为方式
animator.setInterpolator(new LinearInterpolator());
//设置过程的时间
animator.setDuration(1000);
//监听过程,对过程的每个细节进行更新
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取到过程的百分比(进度)
float fraction = animation.getAnimatedFraction();
//根据进度,建立跟view的关系,如view中的一个圆的半径变化
radius = fraction * 100;
//通知view刷新(重点)
invalidate();
}});
//开始动画过程
animator.start();
方法二:Handler消息机制等消息通知的形式。类似的如接口回调等
private float radius;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
//更新view的参数
radius++;
//刷新view
invalidate();
}}};
@Override
protected void onDraw(Canvas canvas) {
//发送消息
handler.sendEmptyMessage(1);
}
方法三: 死循环或postInvalidateDelayed(long delayMilliseconds)
private float radius;
@Override
protected void onDraw(Canvas canvas) {
radius++;
//如hanlder机制,延迟指定时间段后刷新view,从而实现不断更新view的内容
//或使用invalidate();立即刷新view(性能消耗较高)
postInvalidateDelayed(10);
}
方法四:利用时间,时间是平滑的变化过程
private float radius;
//第一次的时间
private long firstTime;
//是否初始化标志
private boolean isInit;
@Override
protected void onDraw(Canvas canvas) {
if (isInit) {
//变化状态
isInit = false;
//记录第一次的时间毫秒值
firstTime = System.currentTimeMillis();
}
//更新view内容
radius = (System.currentTimeMillis() - firstTime) * 2;
//刷新view
invalidate();
}
上一篇: 解决RecyclerView聚焦时滚动到起始处的问题
下一篇: Vue中过渡与动画的案例