前言
这几天又拾起老本行,复习复习Android,才发现忘的差不多了,上午做了一个小Demo,配合Scroller做了一个轮播图,效果如下,但是不知为何,录制的GIF成这样,凑乎一下看看。
原理是继承ViewGroup,然后自己摆放子View,也就是摆放在一条线上,开启一个定时器,每隔X秒通过Scroller进行滚动,当然还有处理Touch事件,在手指按下的时候定时器停止,抬起的时候定时器重新启动。
Scroller简介
我们知道View中提供了scrollTo()和scrollBy()两个方法用来滚动,也就是说任何一个控件都是可以滚动的,他两的区别在于,scrollBy()让View相对于当前的位置滚动一段距离,scrollTo()则是让View相对于初始的位置滚动一段距离。
注意这里的滚动是内容的滚动,不是自身位置。
还有一个类是Scroller,用于处理滚动效果的工具类,但是这个类并不能直接使View滚动,而是提供计算滚动的值,我们拿到这个值在调用scrollTo或scrollBy进行滚动。
Scroller的两个重要方法是startScroll和computeScrollOffset,startScroll()方法是用来初始化滚动数据的,第一个参数是滚动开始时X的坐标,第二个参数是滚动开始时Y的坐标,如果我们的控件只支持X轴滚动,那么Y永远是0,,第三个参数是横向滚动的距离,第四个参数是纵向滚动的距离,通常这个方法后面会调用invalidate()方法来刷新界面。
最后就是重写computeScroll()方法,这个方法根据注释的意思是在父级请求子级更新其mScrollX值时候调用,在这个方法下通过不断调用Scroller的computeScrollOffset()方法来判断滚动操作是否完成了,如果还没完成的话,那就继续调用scrollTo()方法,传入Scroller的curX和curY,这样就完成了内容的平滑滚动。
另外Scroller还支持Interpolator,使滚动更具有效果。
实现
package com.example.androiddemo;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
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 androidx.annotation.NonNull;
import java.util.Timer;
import java.util.TimerTask;
public class BannerView extends ViewGroup {
private static final int INVALIDATE_MSG = 1000;
private static final int DEFAULT_DURATION = 1000;
private static final int DEFAULT_PERIOD = 2000;
private static final String TAG = "TAG";
private int mPeriod;
private int mDuration;
private boolean isTouch = false;
private int mDownX;
private Scroller mScroller;
private Timer mTimer;
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case INVALIDATE_MSG:
nextItem();
break;
}
}
};
public BannerView(Context context) {
super(context);
init();
}
public BannerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPeriod = DEFAULT_PERIOD;
mDuration = DEFAULT_DURATION;
mScroller = new Scroller(getContext());
startTimer(0);
}
private void startTimer(int delay) {
mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
if (!isTouch)
handler.sendEmptyMessage(INVALIDATE_MSG);
}
}, delay, mPeriod);
}
private void nextItem() {
mScroller.startScroll(getScrollX(), 0, getMeasuredWidth(), 0, mDuration);
invalidate();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
for (int i = 0; i View childAt = getChildAt(i);
childAt.layout(left, t, r + left, b);
left += r;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
if (mScroller.isFinished() && getScrollX() == getMeasuredWidth() * (getChildCount() - 1)) {
Log.i(TAG, "computeScroll: " + getScrollX());
scrollTo(0, 0);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = (int) event.getX();
mTimer.cancel();
break;
case MotionEvent.ACTION_UP:
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), 0, dx, 0, mDuration);
invalidate();
startTimer(mPeriod);
break;
case MotionEvent.ACTION_MOVE:
scrollBy((int) (mDownX - event.getX()), 0);
mDownX = (int) event.getX();
break;
}
return true;
}
}
<?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"android:orientation="vertical"android:background="#FFFFFF"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="20dp"tools:context=".MainActivity">
<RelativeLayoutandroid:layout_width="match_parent"android:layout_height="150dp">
<com.example.androiddemo.BannerViewandroid:layout_width="match_parent"android:layout_height="match_parent">
<ImageViewandroid:scaleType="centerCrop"android:src="@drawable/banner1"android:layout_width="match_parent"android:layout_height="match_parent">ImageView>
<ImageViewandroid:scaleType="centerCrop"android:src="@drawable/banner2"android:layout_width="match_parent"android:layout_height="match_parent">ImageView>
<ImageViewandroid:scaleType="centerCrop"android:src="@drawable/banner3"android:layout_width="match_parent"android:layout_height="match_parent">ImageView>
<ImageViewandroid:scaleType="centerCrop"android:src="@drawable/banner4"android:layout_width="match_parent"android:layout_height="match_parent">ImageView>
<ImageViewandroid:scaleType="centerCrop"android:src="@drawable/banner1"android:layout_width="match_parent"android:layout_height="match_parent">ImageView>
com.example.androiddemo.BannerView>
RelativeLayout>
LinearLayout>
- END -