Android自定义控件----继承ViewGroup自定义ViewPager2,使用Scroller实现平滑移动
程序员文章站
2022-06-08 17:15:30
...
在上一篇文章中
Android自定义控件—-继承ViewGroup自定义ViewPager(学习)
中由于使用scrollTo()和scrollBy()这两个方法进行滑动,但是有个问题就是滑动很生硬,所以在这篇文章使用Scroller对象实现平滑的滑动,Scroller对象的使用的基本步骤:
// 1. 创建Scroller的实例
// 2. 调用startScroll()方法来初始化滚动数据并刷新界面
// 3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑(里面代码时固定的)
具体代码如下:
package com.zhh.mybanner;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import com.orhanobut.logger.Logger;
/**
* Created by 16838 on 2018/5/23.
*/
public class ImageViewGroup extends ViewGroup {
// 子视图的个数
private int children;
// 子视图的宽度
private int childwidth;
// 子视图的高度
private int childheight;
// 此时的x值代表的是第一次按下位置的横坐标,每一次移动过程中 移动之前位置的横坐标
private int myX;
// 每张图片的索引
private int index = 0;
// Scroller的用法
// 1. 创建Scroller的实例
// 2. 调用startScroll()方法来初始化滚动数据并刷新界面
// 3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑(里面代码时固定的)
// 定义一个scroller对象
Scroller scroller;
/**
* 控件在xml文件中声明的时候必须要重写这个
*
* @param context
* @param attrs
*/
public ImageViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(getContext());
}
/**
* 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
* 计算滑动
* 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制
*/
@Override
public void computeScroll() {
super.computeScroll();
// 如果返回true,表示动画还没有结束
// 因为前面startScroll,所以只有在startScroll完成时 才会为false
if (scroller.computeScrollOffset()) {
// 产生了动画效果,根据当前值 每次滚动一点
scrollTo(scroller.getCurrX(), 0);
//此时同样也需要刷新View ,否则效果可能有误差
postInvalidate();
} else {
// 动画结束
}
}
/**
* 测量宽度和高度
* 测量父布局的高度和宽度
* 测量----布局----绘制
* 绘制:针对绘制来说,因为我们是自定义的 ImageViewGroup容器,
* 针对容器的绘制,其实就是容器内子控件的绘制过程,那么我们只需要
* 调用系统自带的绘制,也就是说我们不需要重写该方法,调用系统自带的即可。
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 1求出子视图的个数
children = getChildCount();
// 2求出子视图的宽度和高度
if (children == 0) {
// 如果子视图的个数是0,则设置ImageViewGroup的高度和宽度是0
setMeasuredDimension(0, 0);
} else {
// 测量子视图的宽度和高度
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 拿到第一个子视图,绝对存在
View view = getChildAt(0);
// 3根据子视图的宽度和高度,求出ImageViewGroup的宽度和高度
childheight = view.getMeasuredHeight();
childwidth = view.getMeasuredWidth();
int width = childwidth * children;
// 设置ImageViewGroup的宽高
setMeasuredDimension(width, childheight);
}
}
/**
* 参数 changed 表示ImageViewGroup位置发生改变时时true,不发生改变为false
* 设置子布局的位置
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
int leftMargin = 0;
for (int i = 0; i < children; i++) {
// 当位置改变时设置每个子布局的位置
View view = getChildAt(i);
// 设置子布局的位置
// left 和 top 是控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离。
// right 和 bottom是空间右边缘和下边缘相对于父类控件左边缘和上边缘的距离。
// 所以下面这样计算
view.layout(leftMargin, 0, leftMargin + childwidth, childheight);
leftMargin += childwidth;
}
}
}
/**
* 事件处理
* 事件的传递过程中的调用方法:我们需要 调用 容器的拦截方法 onInterceptTouchEvent
* 针对该方法我们可以理解为 如果说 该方法的返回值为true的时候,那么自定义的ImageViewGroup就会处理此次拦截事件
* 如果说返回的值为false的时候,不会接受此次事件的处理过程,将会继续向下传递事件,我们希望ImageViewGroup处理接收事件,那么我们的
* 返回值就是true,如果是true,真正处理该事件的方法是onTouchEvent
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
// 按下一瞬间
case MotionEvent.ACTION_DOWN:
Logger.t("111").d("ACTION_DOWN");
myX = (int) event.getX();
break;
// 移动过程
case MotionEvent.ACTION_MOVE:
// 距离父容器左边的距离,和x轴坐标相等
int moveX = (int) event.getX();
Logger.t("111").d("ACTION_MOVE+move" + moveX);
int distance = moveX - myX;
// scrollTo移动的是坐标,将整个父视图的左上角定为(0,0)
// scrollBy移动的是距离
scrollBy(-distance, 0);
myX = moveX;
break;
// 抬起一瞬间
case MotionEvent.ACTION_UP:
Logger.t("111").d("ACTION_UP");
// 移动的位移,原点减视图左上角坐标 0-左上角坐标
int scrollX = getScrollX();
// 计算索引
index = (scrollX + childwidth / 2) / childwidth;
if (index < 0) {
//说明此时已经滑到左边第一张图片
index = 0;
} else if (index > children - 1) {
//说明此时已经滑动到最后一张图片
index = children - 1;
}
int dx = index * childwidth - scrollX;
// 调用startScroll()方法来初始化滚动数据并刷新界面
// 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达坐标为(startX+dx , startY+dy)出
scroller.startScroll(scrollX, 0, dx, 0, 500);
// 重绘
invalidate();
break;
default:
break;
}
return true;
}
}
参考文章
https://blog.csdn.net/guolin_blog/article/details/48719871
参考视频:
https://www.imooc.com/learn/793
上一篇: dashboard添加可编辑字段
下一篇: Centos7安装完后无法联网的解决方法