欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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();
}