美文网首页
Android,自定义MaskImage

Android,自定义MaskImage

作者: wenju | 来源:发表于2021-02-22 11:14 被阅读0次

主要代码:

public class MaskableFrameLayout extends FrameLayout {

    //Constants
    private static final String TAG = "MaskableFrameLayout";

    private static final int MODE_ADD = 0;
    private static final int MODE_CLEAR = 1;
    private static final int MODE_DARKEN = 2;
    private static final int MODE_DST = 3;
    private static final int MODE_DST_ATOP = 4;
    private static final int MODE_DST_IN = 5;
    private static final int MODE_DST_OUT = 6;
    private static final int MODE_DST_OVER = 7;
    private static final int MODE_LIGHTEN = 8;
    private static final int MODE_MULTIPLY = 9;
    private static final int MODE_OVERLAY = 10;
    private static final int MODE_SCREEN = 11;
    private static final int MODE_SRC = 12;
    private static final int MODE_SRC_ATOP = 13;
    private static final int MODE_SRC_IN = 14;
    private static final int MODE_SRC_OUT = 15;
    private static final int MODE_SRC_OVER = 16;
    private static final int MODE_XOR = 17;

    private Handler mHandler;

    //Mask props
    @Nullable
    private Drawable mDrawableMask = null;
    @Nullable
    private Bitmap mFinalMask = null;

    //Drawing props
    private Paint mPaint = null;
    private PorterDuffXfermode mPorterDuffXferMode = null;

    public MaskableFrameLayout(Context context) {
        super(context);
    }

    public MaskableFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        construct(context, attrs);
    }

    public MaskableFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        construct(context, attrs);
    }

    private void construct(Context context, AttributeSet attrs) {
        mHandler = new Handler();
        setDrawingCacheEnabled(true);
        setLayerType(LAYER_TYPE_SOFTWARE, null); //Only works for software layers
        mPaint = createPaint(false);
        Resources.Theme theme = context.getTheme();
        if (theme != null) {
            TypedArray a = theme.obtainStyledAttributes(
                    attrs,
                    R.styleable.MaskableLayout,
                    0, 0);
            try {
                //Load the mask if specified in xml
                initMask(loadMask(a));
                //Load the mode if specified in xml
                mPorterDuffXferMode = getModeFromInteger(
                        a.getInteger(R.styleable.MaskableLayout_porterduffxfermode, 0));
                initMask(mDrawableMask);
                //Check antiAlias
                if (a.getBoolean(R.styleable.MaskableLayout_anti_aliasing, false)) {
                    //Recreate paint with anti aliasing enabled
                    //This can take a performance hit.
                    mPaint = createPaint(true);
                }
            } finally {
                if (a != null) {
                    a.recycle();
                }
            }
        } else {
            log("Couldn't load theme, mask in xml won't be loaded.");
        }
        registerMeasure();
    }

    @NonNull
    private Paint createPaint(boolean antiAliasing) {
        Paint output = new Paint(Paint.ANTI_ALIAS_FLAG);
        output.setAntiAlias(antiAliasing);
        output.setXfermode(mPorterDuffXferMode);
        return output;
    }

    //Mask functions
    @Nullable
    private Drawable loadMask(@NonNull TypedArray a) {
        final int drawableResId = a.getResourceId(R.styleable.MaskableLayout_mask, -1);
        if (drawableResId == -1) {
            return null;
        }
        return AppCompatResources.getDrawable(getContext(), drawableResId);
    }

    private void initMask(@Nullable Drawable input) {
        if (input != null) {
            mDrawableMask = input;
            if (mDrawableMask instanceof AnimationDrawable) {
                mDrawableMask.setCallback(this);
            }
        } else {
            log("Are you sure you don't want to provide a mask ?");
        }
    }

    @Nullable
    public Drawable getDrawableMask() {
        return mDrawableMask;
    }

    @Nullable
    private Bitmap makeBitmapMask(@Nullable Drawable drawable) {
        if (drawable != null) {
            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
                Bitmap mask = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
                        Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(mask);
                drawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
                drawable.draw(canvas);
                return mask;
            } else {
                log("Can't create a mask with height 0 or width 0. Or the layout has no children and is wrap content");
                return null;
            }
        } else {
            log("No bitmap mask loaded, view will NOT be masked !");
        }
        return null;
    }

    public void setMask(int drawableRes) {
        Resources res = getResources();
        if (res != null) {
            setMask(res.getDrawable(drawableRes));
        } else {
            log("Unable to load resources, mask will not be loaded as drawable");
        }
    }

    public void setMask(@Nullable Drawable input) {
        initMask(input);
        swapBitmapMask(makeBitmapMask(mDrawableMask));
        invalidate();
    }

    public void setPorterDuffXferMode(PorterDuff.Mode mode) {
        this.mPorterDuffXferMode = new PorterDuffXfermode(mode);
    }

    //Once the size has changed we need to remake the mask.
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        setSize(w, h);
    }

    private void setSize(int width, int height) {
        if (width > 0 && height > 0) {
            if (mDrawableMask != null) {
                //Remake the 9patch
                swapBitmapMask(makeBitmapMask(mDrawableMask));
            }
        } else {
            log("Width and height must be higher than 0");
        }
    }

    //Drawing
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mFinalMask != null && mPaint != null) {
            mPaint.setXfermode(mPorterDuffXferMode);
            canvas.drawBitmap(mFinalMask, 0.0f, 0.0f, mPaint);
            mPaint.setXfermode(null);
        } else {
            log("Mask or paint is null ...");
        }
    }

    //Once inflated we have no height or width for the mask. Wait for the layout.
    private void registerMeasure() {
        final ViewTreeObserver treeObserver = MaskableFrameLayout.this.getViewTreeObserver();
        if (treeObserver != null && treeObserver.isAlive()) {
            treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    ViewTreeObserver aliveObserver = treeObserver;
                    if (!aliveObserver.isAlive()) {
                        aliveObserver = MaskableFrameLayout.this.getViewTreeObserver();
                    }
                    if (aliveObserver != null) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                            aliveObserver.removeOnGlobalLayoutListener(this);
                        } else {
                            aliveObserver.removeGlobalOnLayoutListener(this);
                        }
                    } else {
                        log("GlobalLayoutListener not removed as ViewTreeObserver is not valid");
                    }
                    swapBitmapMask(makeBitmapMask(mDrawableMask));
                }
            });
        }
    }

    //Logging
    private void log(@NonNull String message) {
        Log.d(TAG, message);
    }

    //Animation
    @Override
    public void invalidateDrawable(Drawable dr) {
        if (dr != null) {
            initMask(dr);
            swapBitmapMask(makeBitmapMask(dr));
            invalidate();
        }
    }

    @Override
    public void scheduleDrawable(Drawable who, Runnable what, long when) {
        if (who != null && what != null) {
            mHandler.postAtTime(what, when);
        }
    }

    @Override
    public void unscheduleDrawable(Drawable who, Runnable what) {
        if (who != null && what != null) {
            mHandler.removeCallbacks(what);
        }
    }

    private void swapBitmapMask(@Nullable Bitmap newMask) {
        if (newMask != null) {
            if (mFinalMask != null && !mFinalMask.isRecycled()) {
                mFinalMask.recycle();
            }
            mFinalMask = newMask;
        }
    }

    //Utils
    private PorterDuffXfermode getModeFromInteger(int index) {
        PorterDuff.Mode mode = null;
        switch (index) {
            case MODE_ADD:
                if (Build.VERSION.SDK_INT >= 11) {
                    mode = PorterDuff.Mode.ADD;
                } else {
                    log("MODE_ADD is not supported on api lvl " + Build.VERSION.SDK_INT);
                }
            case MODE_CLEAR:
                mode = PorterDuff.Mode.CLEAR;
                break;
            case MODE_DARKEN:
                mode = PorterDuff.Mode.DARKEN;
                break;
            case MODE_DST:
                mode = PorterDuff.Mode.DST;
                break;
            case MODE_DST_ATOP:
                mode = PorterDuff.Mode.DST_ATOP;
                break;
            case MODE_DST_IN:
                mode = PorterDuff.Mode.DST_IN;
                break;
            case MODE_DST_OUT:
                mode = PorterDuff.Mode.DST_OUT;
                break;
            case MODE_DST_OVER:
                mode = PorterDuff.Mode.DST_OVER;
                break;
            case MODE_LIGHTEN:
                mode = PorterDuff.Mode.LIGHTEN;
                break;
            case MODE_MULTIPLY:
                mode = PorterDuff.Mode.MULTIPLY;
                break;
            case MODE_OVERLAY:
                if (Build.VERSION.SDK_INT >= 11) {
                    mode = PorterDuff.Mode.OVERLAY;
                } else {
                    log("MODE_OVERLAY is not supported on api lvl " + Build.VERSION.SDK_INT);
                }
            case MODE_SCREEN:
                mode = PorterDuff.Mode.SCREEN;
                break;
            case MODE_SRC:
                mode = PorterDuff.Mode.SRC;
                break;
            case MODE_SRC_ATOP:
                mode = PorterDuff.Mode.SRC_ATOP;
                break;
            case MODE_SRC_IN:
                mode = PorterDuff.Mode.SRC_IN;
                break;
            case MODE_SRC_OUT:
                mode = PorterDuff.Mode.SRC_OUT;
                break;
            case MODE_SRC_OVER:
                mode = PorterDuff.Mode.SRC_OVER;
                break;
            case MODE_XOR:
                mode = PorterDuff.Mode.XOR;
                break;
            default:
                mode = PorterDuff.Mode.DST_IN;
        }
        log("Mode is " + mode.toString());
        return new PorterDuffXfermode(mode);
    }
}

使用方法

    <com.maskimage.MaskableFrameLayout
        android:layout_width="wrap_content"
        app:porterduffxfermode="DST_IN"
        app:mask="@mipmap/im_bg"
        android:layout_height="wrap_content">

        <ImageView android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@mipmap/im_mask3"/>

    </com..maskimage.MaskableFrameLayout>

相关文章

网友评论

      本文标题:Android,自定义MaskImage

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