本次博客主要介绍的是如何使用自定义ViewGroup实现viewPager 的详细介绍 :
从中你还可以学习到scrollTo() 和scrollBy()的详细使用的方法 ,手势识别器的使用
好了,废话不多比比 开始咯
首先:实现ViewPager的思路:
1.首先需要几张图片,这你可以随便来几张图片,最好宽高都一样 相差别太大
从上图我们就可以拿到图片的left,top,right,bottom的值 ,然后根据ViewGroup特有的Layout()方法将图片的位置进行确定
上代码:
布局文件中:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".widget.LunBoActivity">
<com.example.zidingyidemo.widget.LunboView
android:id="@+id/lunboView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
要实例化的activity:
public class LunBoActivity extends AppCompatActivity {
int arr[] = {R.drawable.first,R.drawable.second,R.drawable.third,R.drawable.four,R.drawable.five};
private LunboView lunboView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lun_bo);
lunboView = findViewById(R.id.lunboView);
for(int i=0;i<arr.length;i++){
ImageView imageView = new ImageView(this);
imageView.setBackgroundResource(arr[i]);
lunboView.addView(imageView);
}
}
}
从上我们可以看到 我们在activity中将我们准备好的图片资源放入了一个数组中,然后再代码中进行imageView的实例化 将准备好的图片资源放入到我们的imageView中了
自定义的View -->精华所在
public class LunboView extends ViewGroup {
public LunboView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
//View的具体位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//拿到孩子 确定孩子的具体位置
for(int i=0;i<getChildCount();i++){//循环遍历孩子个数
View childView = getChildAt(i);//拿到孩子们-->imageView
childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());//确定孩子的具体位置
}
}
}
当完成以上步骤 你就可以将图片进行显示出来了 ,但是 由于我们定义的孩子位置的坐标 当前只能看到第一张图片(占满屏幕)
想要实现左右滑动显示其他的图片 这时我们就要接触到手势识别器(GestureDetector )
手势识别器的详细使用步骤:
* 1.定义出来
* 2.实例化 将想要的方法进行实现(其中有跟多的方法 如滚动事件监听,双击事件监听 按需重写方法就可以)
* 3.在onTouchEvent中注册(传递给手势识别器)
一起来看代码:
public class LunboView extends ViewGroup {
/**
* 手势识别器
* 1.定义出来
* 2.实例化 将想要的方法进行实现
* 3.在onTouchEvent中注册(传递给手势识别器)
*
*/
private GestureDetector detector;//1.定义出来
public LunboView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
scroller = new Scroller(context);
detector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){ //2.实例化 将想要的方法进行实现
/**
*
* @param e1 手指按下
* @param e2 手指抬起
* @param distanceX 距离x
* @param distanceY 距离y
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){
// return super.onScroll(e1, e2, diseX, distanceY);
scrollBy((int) distanceX,getScrollY()); //getScrollY初始值 0
return true;
}
});
}
//View的具体位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//拿到孩子 确定孩子的具体位置
for(int i=0;i<getChildCount();i++){
View childView = getChildAt(i);
childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);//这一步可以省略 因为下面返回了true
detector.onTouchEvent(event);//3.在onTouchEvent中注册(传递给手势识别器)
return true;
}
}
从上面就可以对图片进行滑动 但是会有一个问题 就是当我们滑动未滑动到下一张图片时 会停留再当前你滑动的位置 ,下面我们就解决这个bug 但是之前 还是来介绍一下scrollTo()和scrollBy()
首先通过字面意思我们就可以得知 scrollTo(x,y)是到达指定的位置
源码:
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();
}
}
}
是不是有点懵 ,不急 我们记住他的字面意思 然后我们来看一下scrollBy()
scrollBy(x,y) 他的意思在初始值的状态下移动到指定位置的间隔距离(x,y)
源码:
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
从中我们可以看出 scrollBy源码里面又执行了scrollTo()
其中我们可以看到mScrollX,和mScrollY 他们其实就是我们页面的坐标原点 就是0,0
由此就可以清晰的区分出scrollTo和ScrollBy()
scrollTo(x,y)->到达我们指定的x,y坐标位置
scrollBy(x,y)->达到我们指定的x,y坐标的距离
举个简单的例子:
假设当前我们的坐标值x,y为(20,0) 当我们想要移动到(40,0)这个坐标的时候
scrollTo(40,0); scrollBy(20,0)
当然还有scrollTo()和scrollBy()都是在内部移动的 不会改变当前视图的位置 (很重要)
从上图我们可以清晰的看到 :当我们想要显示下一张图片时我们当前的left,top值 要从之前的(getWidth,0)移动到屏幕(0,0)的位置才可以显示,由此可得 上一张图片要显示在当前屏幕上时 我们的left,top值就从(-getWidth,0)变成了(0,0) 由此我们可以得出结论,在以当前屏幕为参照物的时候,当我们想要显示下一张图片 我们的坐标是减少了的,当我们想要显示上一张图片时 我们的坐标反而是加加了 但是当我们想要显示下一张图片时我们的index是要加一的
然后就可以根据MotionEvent的抬起和按下的时候,获取当前的x坐标,然后计算出偏移的距离,根据偏移的距离进行判断,如果偏移的距离大于整个屏幕宽度的一半就显示下一张图片,反之还显示当前图片
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
detector.onTouchEvent(event);
switch (event.getAction()){
//手指按下
case MotionEvent.ACTION_DOWN:
startX= event.getX();//获取按下的时候的X值
break;
case MotionEvent.ACTION_UP:
float endX = event.getX();//获取抬起的时候的X值
float pian = endX-startX;//x偏移量
int currentindex = current;
Log.i("startend","start"+startX+"endx"+endX);
if((startX-endX)>getWidth()/2){
//显示下一个页面
currentindex++; //注意这里时下标位置
}else if((endX-startX)>getWidth()/2){
currentindex--;
}
//根据下标移动到具体页面
scrollto(currentindex);
break;
}
return true;
}
private void scrollto(int currentindex) {
Log.i("nidaodiyou","@"+getChildCount());
if(currentindex<0){
currentindex = 0;
}
if(currentindex>getChildCount()-1){
currentindex = getChildCount()-1;
}
current = currentindex;
//移动到指定位置(瞬间)
scrollTo(current*getWidth(),getScrollY());
}
其中下标的确认由图清楚可得 我就不再做更多的解释了
这时我们就可以解决这个bug了 但是新的问题又出现了 当我们滑动超过中间的一半 是瞬时滑动到了上一张或者下一张 没有缓慢划过去的那种动画 感觉不美观
这时我们可以调用系统提供的Scroller这个类来解决 这里不做过多的介绍 我就将完整的代码给贴出来
public class LunboView extends ViewGroup {
private float startX;
private int current; //当前页面的下标位置
//private Scroller scroller;
private android.widget.Scroller scroller;
/**
* 手势识别器
* 1.定义出来
* 2.实例化 将想要的方法进行实现
* 3.在onTouchEvent中注册(传递给手势识别器)
*
*/
private GestureDetector detector;
public LunboView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
scroller = new Scroller(context);
detector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
/**
*
* @param e1 手指按下
* @param e2 手指抬起
* @param distanceX 距离x
* @param distanceY 距离y
* @return
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// return super.onScroll(e1, e2, diseX, distanceY);
scrollBy((int) distanceX,getScrollY()); //getScrollY初始值 0
return true;
}
});
}
//View的具体位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//拿到孩子 确定孩子的具体位置
for(int i=0;i<getChildCount();i++){
View childView = getChildAt(i);
childView.layout(i*getWidth(),0,(i+1)*getWidth(),getHeight());
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
detector.onTouchEvent(event);
switch (event.getAction()){
//手指按下
case MotionEvent.ACTION_DOWN:
startX= event.getX();
break;
case MotionEvent.ACTION_UP:
float endX = event.getX();
float pian = endX-startX;
int currentindex = current;
Log.i("startend","start"+startX+"endx"+endX);
if((startX-endX)>getWidth()/2){
//显示下一个页面
currentindex++; //注意这里时下标位置
}else if((endX-startX)>getWidth()/2){
currentindex--;
}
//根据下标移动到具体页面
scrollto(currentindex);
break;
}
return true;
}
private void scrollto(int currentindex) {
Log.i("nidaodiyou","@"+getChildCount());
if(currentindex<0){
currentindex = 0;
}
if(currentindex>getChildCount()-1){
currentindex = getChildCount()-1;
}
current = currentindex;
int distance = current*getWidth()-getScrollX();
scroller.startScroll(getScrollX(),getScrollY(),distance,0);
invalidate(); //导致ondraw()重新执行 还有computeScroll
//移动到指定位置(瞬间)
//scrollTo(current*getWidth(),getScrollY());
}
//
@Override
public void computeScroll() {
// super.computeScroll();
if(scroller.computeScrollOffset()){
float currx = scroller.getCurrX();
scrollTo((int) currx,0);
invalidate(); //再去执行
}
}
}
ok,这样就完成了效果 喜欢的话请点一个小赞👍 你的支持是我最大的动力!!!!