美文网首页Android开发
Android中无限滚动的Image

Android中无限滚动的Image

作者: MuMuXuan | 来源:发表于2016-07-27 23:22 被阅读677次

    前言

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

    效果

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

    1. 分析需求

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

    2. xml资源相关

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

     <resource>
    <declare-styleable name="ScrollingView">
        <attr name="speed" format="dimension" />
        <attr name="src" format="reference" />
    </declare-styleable>
    </resource>
    

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

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

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

    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进行记录。
    向后滚动

    具体代码如下:

    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。
    向前滚动
    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值用来控制。并提供对应的公有方法。


    停止滚动

    具体代码如下:

    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

        本文链接:https://www.haomeiwen.com/subject/sgxajttx.html