Android中无限滚动的Image

前言

这是新开的博客第一篇文章。这一篇针对的是自定义控件。在github上有一个自定义控件的效果如下:

效果

这个水平方向上无限滚动的控件,可以用来制作自定义进度条,或者一些tab效果。
具体使用方法请移步github。
它实现该效果只有50行代码不到,所以写这篇博客来记录该控件的实现过程。

1. 分析需求

  • 设置进去的图片可以水平滚动;
  • 滚动展示的图片可以自行设置,滚动的速度也可以自行设置,;
  • 速度为正,向后滚动(从右向左滚动);为负时,向前滚动(从左向右滚动);

2. xml资源相关

能够自行设置,那么需要去设置自定义属性来控制速度及滚动展示的图片。
在res/values下创建attrs.xml,并采用以下方式定义自定义属性:

 

    
    


speed为速度,src为滚动展示的图片资源。
在layout布局文件中使用自定义属性,首先在根布局view中设置命名空间:

    xmlns:app="http://schemas.android.com/apk/res-auto"

然后在自定义控件中设置自定义属性

Android中无限滚动的Image_第1张图片
layout布局中使用自定义属性

3. java代码

3.1 初始化

创建一个View类,并在构造方法中获得自定义属性speed和图片。凡是在xml中定义的控件,会调用以下形式的构造方法:

public ScrollingView(Context context, AttributeSet attrs) {
    super(context, attrs);
     try{
        speed = typedArray.getDimensionPixelSize(R.styleable.ScrollingView_speed, 1);
        bitmap = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.ScrollingView_src, 0));
    }finally {
        typedArray.recycle();
    }
}

3.2 onMeasure测量

需要设置控件的宽高,不然会出现高度显示不正常。这里采用的方式为:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), bitmap.getHeight());
}

3.3 onDraw绘制

关键点来了,该控件最大的难点就在绘制,我将绘制逻辑分为三个要点:

3.3.1 要点一

如果当前的控件宽度大于要滚动的图片宽度,那么会出现空白,应该需要重复绘制多个图片来填充。


出现空白
  • 解决方案:用一个变量left记录当前canvas绘制bitmap时的左方基坐标,如果left小于控件的宽度,就继续绘制,每绘制一次图片,就让left的值递增,每次递增的值为bitmap的宽度,这种方式可以实现从左往右的绘制图片出来填充满控件。
填补空白

具体代码如下:

protected void onDraw(Canvas canvas) {
    //获取要滚动的图片的宽度
    float layerWidth = bitmap.getWidth();
    //使用一个变量来作为绘制bitmap时的左边基坐标
    float left = 0;
    //如果left不大于控件的宽度,则循环绘制
    while (left < getMeasuredWidth()) {
        canvas.drawBitmap(bitmap, left, 0, null);
        left += layerWidth;
    }
}

3.3.2 要点二

根据speed属性,来设置图片往后退的速度。

  • 解决方案:通过不断修改canvas.drawBitmap()中的left,根据speed,不断的让left坐标变小,并调用重绘方法,从而使得图片不断后退。关于left变小的偏移量,使用全局变量offset进行记录。
Android中无限滚动的Image_第2张图片
向后滚动

具体代码如下:

private float offset;
@Override
protected void onDraw(Canvas canvas) {
    //获取要滚动的图片的宽度
    float layerWidth = bitmap.getWidth();
    //使用一个变量来作为绘制bitmap的左边基坐标
    float left = offset;
    //如果left不大于控件的宽度,则循环绘制
    while (left < getMeasuredWidth()) {
        canvas.drawBitmap(bitmap, left, 0, null);
        left += layerWidth;
    }
    //全局变量offser用来记录left的偏移量
    offset = offset-speed;
    postInvalidate();
}

这样写导致left的偏移量越来越小,代码会在手机屏幕左方看不见的地方疯狂绘制,这显然不太效率。这里多加一个判断,如果offset超过一定的界限,就重置。

protected void onDraw(Canvas canvas) {

    //获取要滚动的图片的宽度
    float layerWidth = bitmap.getWidth();
    if (offset < -layerWidth) {
        offset += (floor(abs(offset) / layerWidth) * layerWidth);
        //offset = 0;
    }
    ...
}

3.3.3 要点三

如果速度设置为负数,图片应该不再后退,而是不断前进。

  • 解决方案:并让左方基坐标不断变大,并改变绘制的方向,改为从右往左的方向绘制图片,就可以让图片变成向前滚了。
    • 如果需要从右向左,那么开始绘制第一个图片时,左方的基坐标不再是从0开始,而是:控件的宽度-图片bitmap的宽度。
    • 在当前的情况下,为了left变量总体趋势不断变小,同时能够不断的让左方基坐标变大,所以在循环当中,左方基坐标改为getMeasureWidth()-bitmap.getWidth()-left。
    • left变量总体趋势不断变小,offset也应该不断变小,而当前speed为负,所以在对offset偏移量的减去speed操作时,对speed采用绝对值abs。
Android中无限滚动的Image_第3张图片
向前滚动
protected void onDraw(Canvas canvas) {
    //获取要滚动的图片的宽度
    float layerWidth = bitmap.getWidth();
    ...
    //如果left不大于控件的宽度,则循环绘制
    while (left < getMeasuredWidth()) {
        canvas.drawBitmap(bitmap, getBitmapLeft(layerWidth, left), 0, null);
        left += layerWidth;
    }
    //全局变量offser用来记录left的偏移量
    if(isStarted){
        offset = offset-abs(speed);
        postInvalidate();
    }
}

/**
 * @param layerWidth bitmap图片的宽度
 * @return
 */
private float getBitmapLeft(float layerWidth, float left) {
    if (speed < 0) {
        return getMeasuredWidth() - layerWidth - left;
    } else {
        return left;
    }
}

3.4 功能完善

为了能够让开发者自由控制图片滚动,项目中还加了一个boolean值用来控制。并提供对应的公有方法。


Android中无限滚动的Image_第4张图片
停止滚动

具体代码如下:

private boolean isStarted = false;

public ScrollingView(Context context, AttributeSet attrs) {
    ...
    start();
}

/**Start the animation*/
public void start() {
    if (!isStarted) {
        isStarted = true;
        postInvalidate();
    }
}

protected void onDraw(Canvas canvas) {
    ...
    //全局变量offser用来记录left的偏移量
    if(isStarted){
        offset = offset-speed;
        postInvalidate();
    }
}

/**Stop the animation*/
public void stop() {
    if (isStarted) {
        isStarted = false;
        invalidate();
    }
}
public boolean isStarted(){
    return isStarted;
}

结束语

有兴趣的小伙伴可以参考这个思路,通过修改drawBitmap方法中top基坐标,实现一下Image在竖直方向上的无限滚动。

你可能感兴趣的:(Android中无限滚动的Image)