天天看点

王学岗高级UI5————Canvas实际案例操作

第一,Drawable概念

鸿阳大神的博客,大家可以参考下

这篇博客也可以参考下

Drawable就是一个可画的对象,表示一种可以在Canvas上进行绘制的抽象的概念,其可能是一张(BitmapDrawable),

也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象,

就可以将这个可画对象当作一块“画布(Canvas)”,在其上面操作可画对象,并最终将这种可画对象显示在画布上,有点类似于“内存画布“。

ImageView imageView = new ImageView(this);
        Drawable drawable = getResources().getDrawable(R.drawable.avft);
        imageView.setImageDrawable(drawable);
           

Drawable 并不是一张图片,而是绘制图片到canvas的工具。

Drawable有三个重要的方法,第一个是draw();这是个抽象方法:public abstract void draw( Canvas canvas);这是个抽象方法,具体逻辑在子类中实现。

第二个:

/**
812       * Specify the level for the drawable.  This allows a drawable to vary its
813       * imagery based on a continuous controller, for example to show progress
814       * or volume level.
815       *
816       * <p>If the new level you are supplying causes the appearance of the
817       * Drawable to change, then it is responsible for calling
818       * {@link #invalidateSelf} in order to have itself redrawn, <em>and</em>
819       * true will be returned from this function.
820       *
821       * @param level The new level, from 0 (minimum) to 10000 (maximum).
822       *
823       * @return Returns true if this change in level has caused the appearance
824       * of the Drawable to change (hence requiring an invalidate), otherwise
825       * returns false.
826       */
827      public final boolean setLevel(@IntRange(from=0,to=10000) int level) {
828          if (mLevel != level) {
829              mLevel = level;
830              return onLevelChange(level);
831          }
832          return false;
833      }
834  
           

官方的解释翻译过来就是:指定可绘制对象的级别。这允许可绘制对象基于连续控制器改变其图像。例如显示进度或音量级别。

可以理解为设置图层,值为0~10000;当然了,有set方法,就有get方法

我们先看下代码

package com.example.lsn5;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.Gravity;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * @author writing
 * @time 2019/12/14 16:13
 * @note
 */
public class RevealDrawable extends Drawable {
    private Drawable selectedImage;
    private Drawable unSelectedImage;


    public RevealDrawable(Drawable selectedImage, Drawable unSelectedImage) {
        this.selectedImage = selectedImage;
        this.unSelectedImage = unSelectedImage;

    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        Rect bounds = getBounds();
        Rect temp = new Rect();
        Gravity.apply(Gravity.LEFT,bounds.width()/2,bounds.height(),bounds,temp);
        canvas.save();
        canvas.clipRect(temp);
        selectedImage.draw(canvas);
        canvas.restore();

        Gravity.apply(Gravity.RIGHT,bounds.width()/2,bounds.height(),bounds,temp);
        canvas.save();
        canvas.clipRect(temp);
        unSelectedImage.draw(canvas);
        canvas.restore();
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
       selectedImage.setBounds(bounds);
       unSelectedImage.setBounds(bounds);
    }
    @Override
    public int getIntrinsicHeight() {
        return unSelectedImage.getIntrinsicHeight();
    }

    @Override
    public int getIntrinsicWidth() {
        return unSelectedImage.getIntrinsicWidth();
    }

    @Override
    public void setAlpha(int i) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return 0;
    }
}


           

核心代码就是draw了,but,我们只需要一行~~~~setAlpha、setColorFilter、getOpacity、draw这几个方法是必须实现的,不过除了draw以为,其他都很简单。getIntrinsicWidth、getIntrinsicHeight主要是为了在View使用wrap_content的时候,提供一下尺寸,默认为-1可不是我们希望的。setBounds就是去设置下绘制的范围。

MainActivity

package com.example.lsn5;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView = findViewById(R.id.image);
        RevealDrawable drawable = new RevealDrawable(getResources().getDrawable(R.drawable.avft),getResources().getDrawable(R.drawable.avft_active));
        imageView.setImageDrawable(drawable);
    }


}

           
<?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"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/image"
        android:background="@color/colorPrimaryDark"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
           

我们看下运行效果

王学岗高级UI5————Canvas实际案例操作

看下原来的两张图片,我们分别截取左右两部分

王学岗高级UI5————Canvas实际案例操作
王学岗高级UI5————Canvas实际案例操作

滑动变色

我们在上面的基础上继续完善我们的代码

package com.dn_alan.myapplication;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;



public class GallaryHorizonalScrollView extends HorizontalScrollView{
    private LinearLayout container;
    private int w;//单张图的宽度

    public GallaryHorizonalScrollView(Context context) {
        super(context);
        init();
    }

    public GallaryHorizonalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public GallaryHorizonalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
        container = new LinearLayout(getContext());
        container.setLayoutParams(lp);
        addView(container);
    }


    /**
     * 渐变图片
     */
    private void reveal() {
        //得到hzw滑出去的横向距离,根据滚动的距离计算出当前滚动到了哪一张图片
        int scrollX = getScrollX();
        //找到两张渐变图片的下标 ----左,右
        //处在中间位置的两张图片
        int indexLeft = scrollX / w;
        int indexRight = indexLeft + 1;
        //偏移了多少
        float trans = scrollX % w;
        int levelLeft = (int) (5000 - Math.abs(trans / w * 5000));
        int levelRight = levelLeft + 5000;

        Log.e("---------------","left = " + levelLeft + "   right = " + levelRight);

        int count = container.getChildCount();
        for (int i = 0; i < count; i++) {
            ImageView iv = (ImageView) container.getChildAt(i);
            if(i == indexLeft){
                iv.setImageLevel(levelLeft);
            }else if(i == indexRight){
                iv.setImageLevel(levelRight);
            }else{
                iv.setImageLevel(0);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //获取一个子View,因为所有的子View的宽高都是一样的。
        View first = container.getChildAt(0);
        //获取子View 的宽,根据滚动距离,计算滑动到了哪个View
        w = first.getWidth();
        int padding = getWidth() / 2 - w / 2;
        //给LinearLayout和hzw之间设置边框距离。
        container.setPadding(padding,0,padding,0);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        reveal();
    }










    public void addViews(){
        container.removeAllViews();
        for (int i = 0; i < mImgIds.length; i++) {
            container.addView(getRevealView(i));
        }

        ImageView childAt = (ImageView) container.getChildAt(0);
        childAt.setImageLevel(5000);
    }

    private View getRevealView(int i) {
        ImageView iv = new ImageView(getContext());
        Drawable d1 = getResources().getDrawable(mImgIds[i]);
        Drawable d2 = getResources().getDrawable(mImgIdsActive[i]);
        RevealDrawable rd = new RevealDrawable(d1,d2, RevealDrawable.HORIZONTAL);
        iv.setImageDrawable(rd);
        return iv;
    }

    private int[] mImgIds = new int[]{ //7个
            R.drawable.avft,
            R.drawable.box_stack,
            R.drawable.bubble_frame,
            R.drawable.bubbles,
            R.drawable.bullseye,
            R.drawable.circle_filled,
            R.drawable.circle_outline,

            R.drawable.avft,
            R.drawable.box_stack,
            R.drawable.bubble_frame,
            R.drawable.bubbles,
            R.drawable.bullseye,
            R.drawable.circle_filled,
            R.drawable.circle_outline
    };
    private int[] mImgIdsActive = new int[]{
            R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
            R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
            R.drawable.circle_outline_active,
            R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
            R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
            R.drawable.circle_outline_active
    };

}

           
package com.dn_alan.myapplication;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.Gravity;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class RevealDrawable extends Drawable {
    private Drawable d1,d2;
    private Rect mRect = new Rect();
    private int mOrientation;

    public static final int HORIZONTAL = 1;
    public static final int VERTICAL = 2;


    public RevealDrawable(Drawable d1, Drawable d2, int orientation){
        this.d1 = d1;
        this.d2 = d2;
        this.mOrientation = orientation;
    }

    @Override
    public int getIntrinsicWidth() {
        return Math.max(d1.getIntrinsicWidth(),d2.getIntrinsicWidth());
    }

    @Override
    public int getIntrinsicHeight() {
        return Math.max(d1.getIntrinsicHeight(),d2.getIntrinsicHeight());
    }
    /**
     * 制提供了画布上下文,那么就还需要提供一个可绘制的区域,下面方法就是用来指定绘制的区域。
     * Drawable在绘制调用draw函数之前必须要先指定绘制的区域,这个区域也是Canvas中要绘制的区域。
     * 一旦用户改变了绘制区域时会激发onBoundsChange方法,派生类可以重载onBoundsChange来实现区域变更的处理。
     */
    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        d1.setBounds(bounds);
        d2.setBounds(bounds);
    }
    //当调用ImageView的setImageLevel()方法时候,会执行这个方法
    //层级发生改变的话可以在这个方法里做些处理,level的值为0-10000

    /**
     * public final boolean setLevel(int level)
     * public final int getLevel()
     * 你可以用这两个的方法来设置显示的级别,以便进行一些绘制时的区间和条件控制,这个属性并不是所有Drawable派生类都能用到,
     *  如果设置有变化则会调用onLevelChange,派生类可以重载onLevelChange来实现级别变化的更新处理:
     */
    @Override
    protected boolean onLevelChange(int level) {
        invalidateSelf();
        return super.onLevelChange(level);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {

        int level = getLevel();
        int gravity = level < 5000 ? Gravity.LEFT : Gravity.RIGHT;
        float ratio = Math.abs(level / 5000f - 1);
   //得到当前自身Drawable的矩形区域,其长和宽就是getIntrinsicWidth和getIntrinsicHeight方法返回的值
        Rect bounds = getBounds();
        int w = bounds.width();
        int h = bounds.height();

        if(mOrientation == HORIZONTAL){
            w *= ratio;
        }else if(mOrientation == VERTICAL){
            h *= ratio;
        }

        Gravity.apply(
                //从左边扣还是从右边扣
                gravity,
                //目标矩形宽
                w,
                //目标矩形高
                h,
                //被扣对象
                bounds,
                //承载对象
                mRect
        );

        canvas.save();
        //裁剪;裁剪了我们想要的绘制区域,矩形来确认需要剪裁的位置
        canvas.clipRect(mRect);
        //把图形画到传入的 Canvas 上
        d1.draw(canvas);
        canvas.restore();

        w = bounds.width();
        h = bounds.height();
        gravity = level > 5000 ? Gravity.LEFT : Gravity.RIGHT;

        if(mOrientation == HORIZONTAL){
            w -= w * ratio;
        }else if(mOrientation == VERTICAL){
            h -= h * ratio;
        }

        Gravity.apply(
                //从左边扣还是从右边扣
                gravity,
                //目标矩形宽
                w,
                //目标矩形高
                h,
                //被扣对象
                bounds,
                //承载对象
                mRect
        );
        canvas.save();
        canvas.clipRect(mRect);
        d2.draw(canvas);
        canvas.restore();

    }

    @Override
    public void setAlpha(int i) {

    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}

           
package com.dn_alan.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
    private ImageView iv;
    private int[] mImgIds = new int[]{ //7个
            R.drawable.avft,
            R.drawable.box_stack,
            R.drawable.bubble_frame,
            R.drawable.bubbles,
            R.drawable.bullseye,
            R.drawable.circle_filled,
            R.drawable.circle_outline,

            R.drawable.avft,
            R.drawable.box_stack,
            R.drawable.bubble_frame,
            R.drawable.bubbles,
            R.drawable.bullseye,
            R.drawable.circle_filled,
            R.drawable.circle_outline
    };
    private int[] mImgIds_active = new int[]{
            R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
            R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
            R.drawable.circle_outline_active,
            R.drawable.avft_active, R.drawable.box_stack_active, R.drawable.bubble_frame_active,
            R.drawable.bubbles_active, R.drawable.bullseye_active, R.drawable.circle_filled_active,
            R.drawable.circle_outline_active
    };

    public Drawable[] revealDrawables;
    protected int level = 5000;
    private GallaryHorizonalScrollView hzv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initView() {
        hzv = (GallaryHorizonalScrollView) findViewById(R.id.hsv);
    }

    private void initData() {
        revealDrawables = new Drawable[mImgIds.length];

        for (int i = 0; i < mImgIds.length; i++) {
            RevealDrawable rd = new RevealDrawable(
                    getResources().getDrawable(mImgIds[i]),
                    getResources().getDrawable(mImgIds_active[i]),
                    RevealDrawable.HORIZONTAL);
            revealDrawables[i] = rd;
        }
        hzv.addViews();
    }


}

           
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">


    <com.dn_alan.myapplication.GallaryHorizonalScrollView
        android:id="@+id/hsv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:background="#AA444444"
        android:scrollbars="none"
        />
</LinearLayout>

           

我们看下效果

王学岗高级UI5————Canvas实际案例操作
王学岗高级UI5————Canvas实际案例操作
王学岗高级UI5————Canvas实际案例操作
王学岗高级UI5————Canvas实际案例操作

我们看下图片资源,两套图片

王学岗高级UI5————Canvas实际案例操作

继续阅读