大家好,又见面了,我是你们的朋友全栈君。
目 录(本篇字数:1631)
-
介绍
学习自定义View,这个对于初学者来说确实有点难度。因为这需要你熟悉View绘制的基本流程,不仅如此,你还需要熟悉手势识别、解决事件冲突等知识。这是一系列综合性的学习,如果想在Android方面进阶,你必须攻克这个首要技能。今天,我们的目的是学习自定义ViewGroup实现ViewPager类似的滑动效果。
先来看一下我们要实现的效果,其实这个和原生ViewPager效果一样的。
- 效果图
此篇是为了学习自定义View为目的文章,因为处于初学阶段,我们先从简单的实现效果一步步进阶。自定义ViewGroup必须重写父类的一个方法onLayout(),我们来看一下这个方法的介绍。
onLayout
void onLayout (boolean changed, int left, int top, int right, int bottom)
Called from layout when this view should assign a size and position to each of its children. Derived classes with children should override this method and call layout on each of their children.
Parameters changed
boolean
: This is a new size or position for this viewleft
int
: Left position, relative to parenttop
int
: Top position, relative to parentright
int
: Right position, relative to parentbottom
int
: Bottom position, relative to parent
大意:这个方法用于对子视图进行布局,分配子视图大小和位置。ViewGroup必须重写这个方法,根据子视图的左上、右下角的坐标来确定其大小和位置。
-
代码及实现
那么,让我们继承ViewGroup来实现这样效果。先看一下我们的MyViewPager类,它简单的对子视图进行了布局。
/**
* @Created by xww.
* @Creation time 2018/8/13.
*/
public class MyViewPager extends ViewGroup {
private GestureDetector mGestureDetector;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new 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) {
scrollBy((int) distanceX, getScrollY());
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
//对于每个子View进行布局
View childView = getChildAt(i);
childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
return true;
}
}
对于每个子视图(图片)进行了全屏幕的占用,我们看onLayout()方法里的代码,先获取了每个子视图,对子视图进行大小和位子的设置。这里我们依照ViewPager,将子视图横向排成一排,每一张图片占一整个屏幕。下面是我们添加图片的代码,不予解释了。
private int[] imgs = {R.drawable.bg_01, R.drawable.bg_02, R.drawable.bg_03, R.drawable.bg_04, R.drawable.bg_05, R.drawable.bg_06};
private void initViewPager() {
for (int img : imgs) {
AppCompatImageView imageView = new AppCompatImageView(getContext());
imageView.setBackgroundResource(img);
myViewpager.addView(imageView);
}
}
这里我用到了GestureDetector类,这个类指明是手势识别器,它内部封装了一些常用的手势操作的接口,比如单机、双击、长按、滚动等。因为我们用到滚动手势,所以我们在这里中断(处理)它的事件。对于scrollTo、scrollBy、Scroller有疑问的可以看我之前的一篇文章:理解scrollTo、scrollBy、Scroller的一些区别及用法,掌握视图滚动方式以及屏幕坐标系
根据手指滑动产生的distanceX,我们进行子视图的滚动,并且在onTouchEvent中处理我们的手势识别器的事件。到了这一步,我们变可以看到它的滑动效果。
-
优化代码
是不是和我们预想的不太一样?动是可以动了,但它不会自己动,还会卡在中间,是不是感觉很不爽。那这样子的话,我们就得借助另一个滚动视图的类Scroller类。同理,这个类的用法在上面一个快捷链接中也有说明,这里我就不讲它是如何使用了。我们看看最终修改后的MyViewPager类。
/**
* @Created by xww.
* @Creation time 2018/8/13.
*/
public class MyViewPager extends ViewGroup {
private GestureDetector mGestureDetector;
private int currentIndex;
private int startX;
private int endX;
private Scroller mScroller;
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
mGestureDetector = new GestureDetector(context, new 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) {
scrollBy((int) distanceX, getScrollY());
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
});
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
//对于每个子View进行布局
View childView = getChildAt(i);
childView.layout(i * getWidth(), t, (i + 1) * getWidth(), b);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
endX = (int) event.getX();
int tempIndex = currentIndex;
if (startX - endX > getWidth() / 2) { //从右往左滑动
tempIndex++;
} else if (endX - startX > getWidth() / 2) { //从左往右滑动
tempIndex--;
}
scrollIndex(tempIndex);
break;
}
return true;
}
/**
* 移动到指定页面
*/
private void scrollIndex(int tempIndex) {
//第一页,无法继续向左滑动
if (tempIndex < 0) {
tempIndex = 0;
}
//同理,最后一页无法向右滑动
if (tempIndex > getChildCount() - 1) {
tempIndex = getChildCount() - 1;
}
currentIndex = tempIndex;
mScroller.startScroll(getScrollX(), 0, currentIndex * getWidth() - getScrollX(), 0);
postInvalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
postInvalidate();
}
}
}
它实现的思路:我们给予每张图片一个下标,当我们将当前页面滚动的距离大于屏幕宽度的一半时,子视图将自动滚动到下一张图;同理,当前页面滚动的距离小于屏幕宽度的一半,那将自动恢复原始状态,不会滚动到下一张图。主要是在onTouchEvent()方法中,标记起始点和滑动后的最终点的坐标距离,用滑动距离比对屏幕的一半宽度,然后在处理是否要切换图片。代码实现不是很难,多于理解它实现的原理,相信你很快就会掌握自定义View。
©原文链接:https://blog.csdn.net/smile_Running/article/details/81634308
@作者博客:_Xu2WeI
@更多博文:查看作者的更多博文
转载于:https://www.cnblogs.com/xww0826/p/10359504.html
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/107363.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...