API Paint - Explication détaillée de Xfermode et PorterDuff (3)


Introduction à cette section :

Dans la section précédente, nous avons découvert le troisième fils de Xfermode : PorterDuff. Il y a un paramètre dans la méthode de construction Xfermode : PorterDuff. .Mode , après avoir regardé 16 modes de mélange d'images, nous avons écrit notre propre code pour vérifier le document 18 modes de mixage différents, dont 18 sont des modes ADD et OVERLAY nouvellement ajoutés ! Bien entendu, il ne suffit pas de simplement vérifier que vous le savez ; Dans cette section, nous allons écrire un exemple pour nous aider à nous familiariser avec la façon d'utiliser PorterDuff.Mode pour nous le fournir en pratique. Ces modes de lecture aléatoire ! L'exemple présenté dans cette section est : la mise en œuvre de graphiques aux coins circulaires et arrondis !

Dans 2.3.4 ImageView (vue d'image) nous avons enfin expliqué le plus simple L'implémentation du dessin d'une ImageView circulaire consiste à appeler clipPath sur l'image pour découper un cercle !

Cette section est implémentée en utilisant le mode DST_IN ​​​​​​dans PorterDuff.Mode Sans plus tarder, commençons cette section ! PS : Les exemples de cette section sont tirés du travail de Hongyang - Android Xfermode implémente en fait des images circulaires et aux coins arrondis De plus, je dois encore publier les rendus PorterDuff.Mode :

1.png


1. Schéma d'effet à réaliser et analyse du processus de mise en œuvre :

Schéma de rendu après opération :

2.png

Eh bien, ce qui précède est un effet que nous voulons obtenir, grâce à ce mode PorterDuff.Mode.DST_IN ! Analysons le processus de mise en œuvre :

  • Étape 1 : Xfermode n'est rien de plus que deux couches de graphiques. Celui dessiné en premier s'appelle le graphe DST (graphe cible), et celui dessiné plus tard s'appelle le graphe SRC (graphe original que nous voulons réaliser). Pour les cercles ou les coins arrondis, nous pouvons d'abord dessiner l'image à afficher (DST), et ici nous la définissons via l'attribut src ; Dessinez ensuite le cercle et le congé (SRC). La partie que nous voulons afficher est l'endroit où ils se croisent, et c'est le contenu de la partie image. Choisissez donc : le mode DST_IN !
  • Étape 2 : Eh bien, maintenant que nous connaissons le principe, nous devons ensuite considérer les problèmes liés à la personnalisation d'ImageView :
  • Si la vue que nous voulons dessiner est arrondie ou circulaire, nous devons ajouter un attribut pour juger, et des coins arrondis Vous avez également besoin d'un rayon de congé Paramètres, afin que nous puissions utiliser des attributs personnalisés (attrs.xml), puis personnaliser la méthode de construction de la vue pour Supprimez ces paramètres !
  • Vient ensuite le calcul de la taille de l'image : Tout d'abordSi nous définissons un cercle, nous devons rendre la largeur et la hauteur cohérentes, et la valeur minimale prévaudra. ceci dans la méthode onMesure() Appelez getMeasuredXxx() pour obtenir la largeur et la hauteur, voyez laquelle est la plus petite, appelez setMeasuredDimension(x, x); Ensuite, nous obtenons la largeur et la hauteur de l'image dans la méthode onDraw(), puis calculons le rapport de mise à l'échelle en fonction de la largeur et de la hauteur de l'image, ainsi que de la largeur et de la hauteur de la vue, Si la largeur et la hauteur de l'image ne correspondent pas à la largeur et à la hauteur de la vue, la largeur et la hauteur de l'image résultante doivent être supérieures à la largeur et à la hauteur de la vue, alors prenez la valeur la plus élevée !
  • Ensuite vient au dessin de l'image. Définissez une méthode pour dessiner des graphiques. Ensuite, après avoir initialisé le pinceau, définissez setXfermode sur. PorterDuff.Mode.DST_IN, dessinez d'abord l'image, puis dessinez le graphique
  • Enfin , certaines choses pour la mise en cache de l'image sont utilisées ici pour mettre en cache l'image afin d'éviter d'allouer de la mémoire à chaque fois surDraw. Et redessinez, enfin videz le cache en invalidant !

Le processus général de mise en œuvre est comme ci-dessus. Il sera beaucoup plus facile de lire le code après avoir connu le processus !


2. Implémentation du code :

Attributs de contrôle personnalisés : res/attrs.xml :

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="Radius" format="dimension"/>
        <attr name="type">
            <enum name="circle" value="0"/>
            <enum name="round" value="1"/>
        </attr>
    </declare-styleable>
</resources>

Puis l'ImageView personnalisé : CircleImageView.java :

/**
 * Created by Jay on 2015/10/25 0025.
 */
public class CircleImageView extends ImageView {

    private Paint mPaint;
    private Xfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
    private Bitmap mMaskBitmap;
    private WeakReference<Bitmap> mWeakBitmap;

    //图片相关的属性
    private int type;                           //类型,圆形或者圆角
    public static final int TYPE_CIRCLE = 0;
    public static final int TYPE_ROUND = 1;
    private static final int BODER_RADIUS_DEFAULT = 10;     //圆角默认大小值
    private int mBorderRadius;                  //圆角大小


    public CircleImageView(Context context) {
        this(context, null);
    }

    public CircleImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        //取出attrs中我们为View设置的相关值
        TypedArray tArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);
        mBorderRadius = tArray.getDimensionPixelSize(R.styleable.CircleImageView_Radius, BODER_RADIUS_DEFAULT);
        type = tArray.getInt(R.styleable.CircleImageView_type, TYPE_CIRCLE);
        tArray.recycle();
    }

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


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (type == TYPE_CIRCLE) {
            int width = Math.min(getMeasuredWidth(), getMeasuredHeight());
            setMeasuredDimension(width, width);    //设置当前View的大小
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {

        //在缓存中取出bitmap
        Bitmap bitmap = mWeakBitmap == null ? null : mWeakBitmap.get();
        if (bitmap == null || bitmap.isRecycled()) {
            //获取图片宽高
            Drawable drawable = getDrawable();
            int width = drawable.getIntrinsicWidth();
            int height = drawable.getIntrinsicHeight();

            if (drawable != null) {
                bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
                Canvas drawCanvas = new Canvas(bitmap);
                float scale = 1.0f;
                if (type == TYPE_ROUND) {
                    scale = Math.max(getWidth() * 1.0f / width, getHeight()
                            * 1.0f / height);
                } else {
                    scale = getWidth() * 1.0F / Math.min(width, height);
                }
                //根据缩放比例,设置bounds,相当于缩放图片了
                drawable.setBounds(0, 0, (int) (scale * width),
                        (int) (scale * height));

                drawable.draw(drawCanvas);
                if (mMaskBitmap == null || mMaskBitmap.isRecycled()) {
                    mMaskBitmap = getBitmap();
                }

                mPaint.reset();
                mPaint.setFilterBitmap(false);
                mPaint.setXfermode(mXfermode);

                //绘制形状
                drawCanvas.drawBitmap(mMaskBitmap, 0, 0, mPaint);

                //bitmap缓存起来,避免每次调用onDraw,分配内存
                mWeakBitmap = new WeakReference<Bitmap>(bitmap);

                //绘制图片
                canvas.drawBitmap(bitmap, 0, 0, null);
                mPaint.setXfermode(null);

            }
        }
        if (bitmap != null) {
            mPaint.setXfermode(null);
            canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint);
            return;
        }
    }

    //缓存Bitmap,避免每次OnDraw都重新分配内存与绘图
    @Override
    public void invalidate() {
        mWeakBitmap = null;
        if (mWeakBitmap != null) {
            mMaskBitmap.recycle();
            mMaskBitmap = null;
        }
        super.invalidate();
    }

    //定义一个绘制形状的方法

    private Bitmap getBitmap() {
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
                Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);   //抗锯齿
        paint.setColor(Color.BLACK);
        if (type == TYPE_ROUND) {
            canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()),
                    mBorderRadius, mBorderRadius, paint);
        } else {
            canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, paint);
        }
        return bitmap;
    }
}

Enfin, appelez-le dans le fichier de mise en page : activity_main.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.jay.xfermodedemo1.CircleImageView
        android:layout_width="160dp"
        android:layout_height="240dp"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_bg_meizi2"
        app:type="circle" />

    <com.jay.xfermodedemo1.CircleImageView
        android:layout_width="160dp"
        android:layout_height="280dp"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_bg_meizi1"
        app:Radius="30dp"
        app:type="round" />
    
</LinearLayout>

D'accord, je ne comprends pas le code de une fois, vous comprendrez après l'avoir lu deux fois de plus~


3. Téléchargez l'exemple de code de cette section :

XfermodeDemo1.zip


Résumé de cette section :

Dans cette section, nous avons expliqué le premier exemple d'application de Xfermode et PorterDuff, en définissant le mode DST_IN pour y parvenir Personnalisation de l'image ronde et arrondie ImageView, je pense que tout le monde connaît déjà l'application simple de PorterDuff. Frappez pendant que le fer est chaud. Dans la section suivante, nous écrireons un exemple pour mettre en pratique nos compétences ~ D'accord, c'est tout, merci ~