Paint API - Xfermode 및 PorterDuff에 대한 자세한 설명(3)


이 섹션 소개:

이전 섹션에서 Xfermode의 셋째 아들인 PorterDuff에 대해 배웠습니다. Xfermode 구성 방법에는 하나의 매개변수인 PorterDuff.Mode가 있습니다. 문서를 확인하기 위해 코드를 직접 작성했습니다. 18개의 다양한 믹싱 모드 중 18개는 새로 추가된 ADD 및 OVERLAY 모드입니다! 물론, 단순히 알고 있다는 사실을 확인하는 것만으로는 충분하지 않습니다. 이 섹션에서는 실제로 PorterDuff.Mode를 사용하여 제공하는 방법을 익히는 데 도움이 되는 예제를 작성하겠습니다. 이 셔플 모드! 이 섹션에 가져온 예는 원형 및 둥근 모서리 그래픽의 구현입니다!

In 2.3.4 ImageView(이미지 보기) 드디어 가장 간단한 것을 설명했습니다. 원형 ImageView 그리기의 구현은 그림에서 클립패스를 호출하여 원을 자르는 것입니다!

이 섹션은 PorterDuff.ModeDST_IN 모드를 사용하여 구현되었습니다. 이제 이 섹션을 시작하겠습니다. 추신: 이 섹션의 예는 Hongyang의 작업에서 가져온 것입니다. - Android 렌더링 다이어그램 및 구현 프로세스 분석:

작업 후 렌더링 다이어그램1.png:


위는 우리가 달성하려는 효과입니다. 이

PorterDuff.Mode.DST_IN 모드를 통해! 구현 프로세스를 분석해 보겠습니다.

  • 1단계: Xfermode는 그래프의 두 레이어에 지나지 않습니다. 먼저 그려진 것을 DST 그래프(대상 그래프)라고 하고 나중에 그리는 것을 SRC 그래프(원본 그래프)라고 합니다. 원이나 둥근 모서리의 경우 먼저 표시할 이미지(DST)를 그릴 수 있으며 여기서는 src 속성을 통해 설정합니다. 그런 다음 원과 필렛(SRC)을 그립니다. 표시하려는 부분이 교차하는 부분이며 그림 부분의 내용입니다. 그러니 선택하세요: DST_IN 모드!
  • 2단계: 이제 원리를 알았으니 다음으로 ImageView 사용자 정의와 관련된 문제를 고려해야 합니다.
  • 그리고 싶은 뷰가 둥글거나 원형이라면 판단할 속성을 추가해야 하며 둥근 모서리에도 원. 코너 반경. 매개변수를 사용하여 사용자 정의 속성(attrs.xml)을 사용한 다음 보기 구성 방법을 사용자 정의할 수 있습니다. 이 매개변수를 제거하세요!
  • 그런 다음 이미지 크기를 계산합니다. 우선원을 설정하는 경우 너비와 높이를 일관되게 만들어야 하며 최소값에 따라 onMesure() 메서드를 사용할 수 있습니다. 너비와 높이를 얻으려면 getMeasuredXxx()를 호출하고, 어느 것이 더 작은지 확인하고, setMeasuredDimension(x, x)를 호출하여 너비와 높이를 설정하세요! 그런 다음 onDraw() 메서드에서 이미지 너비와 높이를 가져온 다음 이미지 너비와 높이, 뷰 너비와 높이에 따라 크기 조정 비율을 계산합니다. 그림의 너비와 높이가 View의 너비와 높이와 일치하지 않으면 결과 그림의 너비와 높이는 View의 너비와 높이보다 커야 하므로 더 큰 값을 취하세요!
  • 다음은 그림 그리기입니다. 그런 다음 브러시를 초기화한 후 setXfermode를 다음으로 설정합니다. PorterDuff.Mode.DST_IN, 먼저 그림을 그린 다음 그래픽을 그립니다.
  • 마지막으로 는 이미지 캐싱에 관한 몇 가지 사항입니다. 여기서 WeakReference는 Draw에서 매번 메모리 할당을 피하기 위해 이미지를 캐시하는 데 사용됩니다. 그리고 다시 그려서 마침내 무효화에서 캐시를 지웁니다!

일반적인 구현 과정은 위와 같습니다. 과정을 알고 나면 코드를 보는 것이 훨씬 간단해질 것입니다!


2. 코드 구현:

사용자 정의 컨트롤 속성: 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>

그런 다음 사용자 정의 ImageView: 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;
    }
}

마지막으로 레이아웃 파일에서 호출합니다: 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>

좋아, 코드를 한 번 이해하지 못하더라도 두 번 읽으면 이해할 수 있을 것이다~


3 이 섹션의 코드 샘플을 다운로드하세요:

XfermodeDemo1.zip


이 섹션 요약 :

이 섹션에서는 Xfermode 및 PorterDuff의 첫 번째 적용 예를 설명하고 DST_IN 모드를 설정하여 둥글고 둥근 이미지 ImageView의 사용자 정의, 이미 PorterDuff의 간단한 응용 프로그램에 모두가 익숙하다고 생각합니다. 쇠가 뜨거울 때 쳐보세요. 다음에는 실력을 연습할 수 있는 예문을 작성해보겠습니다~알겠습니다. 감사합니다~