王學崗高級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>

我們看下運行效果
在這裏插入圖片描述
看下原來的兩張圖片,我們分別截取左右兩部分
左側圖片
在這裏插入圖片描述

滑動變色

我們在上面的基礎上繼續完善我們的代碼

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>

我們看下效果
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
我們看下圖片資源,兩套圖片
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章