Android画不规则形状

【Android】0行代码实现任意形状图片展示--android-anyshape 前言在Android开发中, 我们经常会遇到一些场景, 需要以一些特殊的形状显示图片, 比如圆角矩形、圆形等等。关于如何绘制这类形状, 网上已经有很多的方案,比如自定义控件重写onDraw方法, 通过canvas的各种draw方法进行绘制等。那么, 更复杂的图形呢?比如,五角星?比如组合图形?又或者是各种奇奇怪怪的不规则图形呢?有同学会说, 如果已知不规则图形的具体形状, 那我们就可以通过连接顶点的 阅读详情

经常会在移动应用中看到类似下图的各种图片:

这样的图形在Android上要怎么实现呢?在Android系统中,目前主要有三种方式可以实现上图的形状,下面一一介绍。

一、PorterDuffXfermode方式

之前的博客曾经介绍过用这种方式画圆形头像,实际上,它不仅可以用来画圆形头像,还可以实现任意形状。首先来复习一下16中效果:

1.PorterDuff.Mode.CLEAR    所绘制不会提交到画布上
 2.PorterDuff.Mode.SRC     显示上层绘制图片
 3.PorterDuff.Mode.DST     显示下层绘制图片
 4.PorterDuff.Mode.SRC_OVER  正常绘制显示,上下层绘制叠盖。
 5.PorterDuff.Mode.DST_OVER  上下层都显示。下层居上显示。
 6.PorterDuff.Mode.SRC_IN    取两层绘制交集。显示上层。
 7.PorterDuff.Mode.DST_IN    取两层绘制交集。显示下层。
 8.PorterDuff.Mode.SRC_OUT   取上层绘制非交集部分。
 9.PorterDuff.Mode.DST_OUT   取下层绘制非交集部分。
 10.PorterDuff.Mode.SRC_ATOP  取下层非交集部分与上层交集部分
 11.PorterDuff.Mode.DST_ATOP  取上层非交集部分与下层交集部分
 12.PorterDuff.Mode.XOR     异或:去除两图层交集部分
 13.PorterDuff.Mode.DARKEN   取两图层全部区域,交集部分颜色加深
 14.PorterDuff.Mode.LIGHTEN   取两图层全部,点亮交集部分颜色
 15.PorterDuff.Mode.MULTIPLY  取两图层交集部分叠加后颜色
 16.PorterDuff.Mode.SCREEN    取两图层全部区域,交集部分变为透明色

我们的实现思路就是绘制两个图层,利用蒙版效果,取两个图形的交集,就可以实现上图的各种形状。所以,这里我们要用到属性SRC_IN或DST_IN,并需要两个图层,一个是形状图层,一个是显示图层,并且显示图层完全覆盖形状图层。那么,如果形状图层在,显示图层在上,就应该选择SRC_IN属性。

采用PorterDuffXfermode方式的源码如下:

public class IrregularShapeImageView extends android.support.v7.widget.AppCompatImageView {

    private Bitmap backgroundBitmap;

    private Bitmap mBitmap;

    private int viewWidth;

    private int viewHeight;


    public IrregularShapeImageView(Context context) {
        this(context, null, 0);
    }

    public IrregularShapeImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        viewWidth = MeasureSpec.getSize(widthMeasureSpec);
        viewHeight = MeasureSpec.getSize(heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBitmap != null && backgroundBitmap != null) {

            int min = Math.min(viewWidth, viewHeight);
            backgroundBitmap = Bitmap.createScaledBitmap(backgroundBitmap, min, min, false);
            mBitmap = Bitmap.createScaledBitmap(mBitmap, min, min, false);

            canvas.drawBitmap(createImage(), 0, 0, null);
        }
    }

    private Bitmap createImage() {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        Bitmap finalBmp = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);

        Canvas canvas = new Canvas(finalBmp);

        canvas.drawBitmap(backgroundBitmap, 0, 0, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

        canvas.drawBitmap(mBitmap, 0, 0, paint);
        return finalBmp;
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        mBitmap = bm;
        setBitmaps();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        mBitmap = getBitmapFromDrawable(drawable);
        setBitmaps();
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
        setBitmaps();
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        mBitmap = getBitmapFromDrawable(getDrawable());
        setBitmaps();
    }

    private void setBitmaps() {
        if (null == getBackground()) {
            throw new IllegalArgumentException(String.format("background is null."));
        } else {
            backgroundBitmap = getBitmapFromDrawable(getBackground());
            invalidate();
        }
    }

    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        super.setScaleType(ScaleType.CENTER_CROP);
        if (drawable == null) {
            return null;
        }
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
        try {
            Bitmap bitmap;
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }

二、BitmapShader方式

Google官方的说法:Shader used to draw a bitmap as a texture. The bitmap can be repeated or mirrored by setting the tiling mode.

意思就是把bitmap当做纹理来用,可以重复,镜像,其实还有一种,拉伸。

BitmapShader 的构造函数

public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY)
三个参数:bitmap 指的是要作为纹理的图片,tileX 指的是在x方向纹理的绘制模式,tileY 指的是Y方向上的绘制模式。

使用BitmapShader实现不规则图形的源码如下:

public class MultiView extends android.support.v7.widget.AppCompatImageView {
    /**
     * 图片的类型,圆形or圆角or多边形
     */
    private Context mContext;

    /**
     * 传输类型
     */
    private int type;

    /**
     * 圆形
     */
    public static final int TYPE_CIRCLE = 0;

    /**
     * 圆角
     */
    public static final int TYPE_ROUND = 1;

    /**
     * 多边形
     */
    public static final int TYPE_MULTI = 3;

    /*
     * 外多边形
     */
    public static final int TYPE_MULTI2 = 4;

    /**
     * 默认多边形角的个数
     */
    public static final int ANGLECOUNT = 5;

    /**
     * 默认开始绘制的角度
     */
    public static final int CURRENTANGLE = 180;

    /**
     * 多边形的半径
     */
    private int startRadius;

    /**
     * 多边形角的个数
     */
    private int angleCount;

    private int[] angles;

    /**
     * 开始绘制的角度
     */
    private int currentAngle;

    /**
     * 存储角位置的集合
     */
    private List<PointF> pointFList = new ArrayList<>();

    /**
     * 圆角大小的默认值
     */
    private static final int BODER_RADIUS_DEFAULT = 10;

    /**
     * 圆角的大小
     */
    private int mBorderRadius;

    /**
     * 绘图的Paint
     */
    private Paint mBitmapPaint;

    /**
     * 圆角的半径
     */
    private int mRadius;

    /**
     * 3x3 矩阵,主要用于缩小放大
     */
    private Matrix mMatrix;

    /**
     * 渲染图像,使用图像为绘制图形着色
     */
    private BitmapShader mBitmapShader;

    /**
     * view的宽度
     */
    private int mWidth;
    private RectF mRoundRect;

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

    public MultiView(Context context, AttributeSet attrs) {

        this(context, attrs, 0);

    }

    public MultiView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        this.mContext = context;

        init(context, attrs);

    }

    public void init(Context context, AttributeSet attrs) {
        mMatrix = new Matrix();
        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);

        TypedArray typedArray = context.obtainStyledAttributes(attrs,
                R.styleable.RoundImageView);

        mBorderRadius = typedArray.getDimensionPixelSize(
                R.styleable.RoundImageView_borderRadius, (int) TypedValue
                        .applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                                BODER_RADIUS_DEFAULT, getResources()
                                        .getDisplayMetrics()));// 默认为10dp
        type = typedArray.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);// 默认为Circle
        angleCount = typedArray.getInt(R.styleable.RoundImageView_angleCount, ANGLECOUNT);
        currentAngle = typedArray.getInt(R.styleable.RoundImageView_currentAngle, currentAngle);

        typedArray.recycle(); //回收之后对象可以重用
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        /**
         * 如果类型是圆形或多边形,则强制改变view的宽高一致,以小值为准
         */
        if (type == TYPE_CIRCLE) {
            mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());
            mRadius = mWidth / 2;
            setMeasuredDimension(mWidth, mWidth);
        }

        if (type == TYPE_MULTI || type == TYPE_MULTI2) {
            mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight());

            setMeasuredDimension(mWidth, mWidth);

            angles = new int[angleCount];

            for (int i = 0; i < angleCount; i++) {
                int partOfAngle = 360 / angleCount; //每个顶点的角度
                angles[i] = currentAngle + partOfAngle * i;

                startRadius = mWidth / 2;
                float x = (float) (Math.sin(Math.toRadians(angles[i])) * startRadius);
                float y = (float) (Math.cos(Math.toRadians(angles[i])) * startRadius);
                pointFList.add(new PointF(x, y));
            }
        }

    }

    /**
     * 初始化BitmapShader
     */
    private void setUpShader() {
        Drawable drawable = getDrawable();
        if (drawable == null) {
            return;
        }

        Bitmap bmp = drawableToBitamp(drawable);
        // 将bmp作为着色器,就是在指定区域内绘制bmp
        mBitmapShader = new BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        float scale = 1.0f;
        if (type == TYPE_CIRCLE) {
            // 拿到bitmap宽或高的小值
            int bSize = Math.min(bmp.getWidth(), bmp.getHeight());
            scale = mWidth * 1.0f / bSize;

        } else if (type == TYPE_ROUND) {
            if (!(bmp.getWidth() == getWidth() && bmp.getHeight() == getHeight())) {
                // 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;
                scale = Math.max(getWidth() * 1.0f / bmp.getWidth(), getHeight() * 1.0f / bmp.getHeight());
            }

        } else if (type == TYPE_MULTI || type == TYPE_MULTI2) {
            // 拿到bitmap宽或高的小值
            int bSize = Math.min(bmp.getWidth(), bmp.getHeight());
            scale = mWidth * 1.0f / bSize;
        }
        // shader的变换矩阵,我们这里主要用于放大或者缩小
        mMatrix.setScale(scale, scale);

        // 设置变换矩阵
        mBitmapShader.setLocalMatrix(mMatrix);
        // 设置shader
        mBitmapPaint.setShader(mBitmapShader);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null) {
            return;
        }
        setUpShader();

        if (type == TYPE_ROUND) {
            canvas.drawRoundRect(mRoundRect, mBorderRadius, mBorderRadius,
                    mBitmapPaint);
        } else if (type == TYPE_MULTI) {
            //canvas.translate(startRadius,startRadius);

            Path mPath = drawPath();

            canvas.drawPath(mPath, mBitmapPaint);
        } else if (type == TYPE_MULTI2) {
            Path mPath = drawPath2();

            canvas.drawPath(mPath, mBitmapPaint);
        } else {
            canvas.drawCircle(mRadius, mRadius, mRadius, mBitmapPaint);
        }
    }

    /**
     * @return 多边形路径
     */
    private Path drawPath2() {
        Path mPath = new Path();
        mPath.moveTo(pointFList.get(0).x, pointFList.get(0).y);
        for (int i = 1; i < angleCount; i++) {
            mPath.lineTo(pointFList.get(i).x, pointFList.get(i).y);
        }

        mPath.offset(startRadius, startRadius);
        return mPath;
    }

    /**
     * @return 多边形路径
     */
    private Path drawPath() {
        Path mPath = new Path();
        mPath.moveTo(pointFList.get(0).x, pointFList.get(0).y);
        for (int i = 2; i < angleCount; i++) {
            if (i % 2 == 0) {// 除以二取余数,余数为0则为偶数,否则奇数
                mPath.lineTo(pointFList.get(i).x, pointFList.get(i).y);
            }

        }

        if (angleCount % 2 == 0) {  //偶数,moveTo
            mPath.moveTo(pointFList.get(1).x, pointFList.get(1).y);
        } else {                    //奇数,lineTo
            mPath.lineTo(pointFList.get(1).x, pointFList.get(1).y);
        }

        for (int i = 3; i < angleCount; i++) {
            if (i % 2 != 0) {
                mPath.lineTo(pointFList.get(i).x, pointFList.get(i).y);
            }
        }

        mPath.offset(startRadius, startRadius);
        return mPath;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // 圆角图片的范围
        if (type == TYPE_ROUND)
            mRoundRect = new RectF(0, 0, w, h);
    }

    /**
     * drawable转bitmap
     *
     * @param drawable
     * @return
     */
    private Bitmap drawableToBitamp(Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bd = (BitmapDrawable) drawable;
            return bd.getBitmap();
        }
        int w = drawable.getIntrinsicWidth();
        int h = drawable.getIntrinsicHeight();
        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, w, h);
        drawable.draw(canvas);
        return bitmap;
    }

    private static final String STATE_INSTANCE = "state_instance";
    private static final String STATE_TYPE = "state_type";
    private static final String STATE_BORDER_RADIUS = "state_border_radius";

    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable(STATE_INSTANCE, super.onSaveInstanceState());
        bundle.putInt(STATE_TYPE, type);
        bundle.putInt(STATE_BORDER_RADIUS, mBorderRadius);
        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            super.onRestoreInstanceState(((Bundle) state)
                    .getParcelable(STATE_INSTANCE));
            this.type = bundle.getInt(STATE_TYPE);
            this.mBorderRadius = bundle.getInt(STATE_BORDER_RADIUS);
        } else {
            super.onRestoreInstanceState(state);
        }

    }

    public void setType(int type) {
        if (this.type != type) {
            this.type = type;
            if (this.type != TYPE_ROUND && this.type != TYPE_CIRCLE && this.type != TYPE_MULTI && this.type != TYPE_MULTI2) {
                this.type = TYPE_CIRCLE;
            }
            requestLayout();
        }

    }

}

自定义属性如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RoundImageView">
        <attr name="borderRadius" format="dimension" />
        <attr name="type">
            <enum name="circle" value="0" />
            <enum name="round" value="1" />
            <enum name="multi" value="3" />
            <enum name="multi2" value="4" />
        </attr>
        <attr name="angleCount" format="integer" />
        <attr name="currentAngle" format="integer" />
    </declare-styleable>
</resources>

三、ClipPath方式

Canvas类的ClipPath方法:

boolean    clipPath(Path path)
Intersect the current clip with the specified path.

clipPath方法的作用是“切割画布”。利用这个特性,也可以实现不规则形状。比如,实现一个三角形的代码如下:

public class TriangleView extends android.support.v7.widget.AppCompatImageView {
    private Context mContext;
    private int mWidth;
    private int mHeight;

    // 三角形的模式以直角点所在的点位置为模式名
    public static final int TYPE_LEFT_TOP = 0;
    public static final int TYPE_RIGHT_BOTTOM = 1;
    public static final int TYPE_RIGHT_TOP = 2;
    public static final int TYPE_RIGHT_MIDDLE = 3;
    public static final int TYPE_LEFT_BOTTOM = 4;
    public static final int TYPE_RIGHT_BOTTOM_SMAILL = 5;

    private int mMode;
    private int mColor;
    private String mText;
    // 三个点的顺序,从leftTop开始计算,顺时针数过去,依次三个点
    private int mPointOne[];
    private int mPointTwo[];
    private int mPointThree[];

    private Path mPath;
    private Path mTextPath;
    private Paint mPaint;

    private boolean isClicked = false; //是否被按下

    public TriangleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mPath = new Path();
        mTextPath = new Path();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mColor = Color.parseColor("#FFFFFF");
        mPaint.setColor(mColor);
    }

    public void setMode(int model) {
        mMode = model;
        postInvalidate();
    }

    public void setColor(int color) {
        mColor = color;
        postInvalidate();
    }
    public void setText(String text) {
        mText = text;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.clipPath(mPath,INTERSECT);
        super.onDraw(canvas);
        if(!isClicked) {
            mPaint.setColor(Color.argb(180, 0, 0, 0));
            canvas.drawPath(mPath, mPaint);
        }
        if(mText != null) {
            mPaint.setFakeBoldText(true);
            mPaint.setTextSize(sp2px(mContext,13));
            mPaint.setColor(mColor);
            float tw = mPaint.measureText(mText);
            float th = mPaint.measureText(mText)/mText.length();
            switch (mMode) {
                case TYPE_LEFT_TOP:
                    canvas.drawText(mText, (mWidth - tw) / 5f, mHeight / 3f, mPaint);
                    break;
                case TYPE_RIGHT_BOTTOM:
                    canvas.drawText(mText, (mWidth - tw)*4/5f, mHeight*4/5f, mPaint);
                    break;
                case TYPE_RIGHT_TOP:
                    canvas.drawText(mText, (mWidth - tw)*3/4f, mHeight/3f, mPaint);
                    break;
                case TYPE_RIGHT_MIDDLE:
                    canvas.drawText(mText, (mWidth*0.92f - tw)/2f, (mHeight+th)/2f, mPaint);
                    break;
                case TYPE_LEFT_BOTTOM:
                    mTextPath.moveTo((1.4f*mHeight-tw-2*th)/2.8f,(1.4f*mHeight-tw+2*th)/2.8f);
                    mTextPath.lineTo((1.4f*mHeight+tw-2*th)/2.8f,(1.4f*mHeight+tw+2*th)/2.8f);
                    canvas.drawTextOnPath(mText,mTextPath,0, 0, mPaint);
                    break;
                case TYPE_RIGHT_BOTTOM_SMAILL:
                    mTextPath.moveTo((1.4f*mHeight-tw+2*th)/2.8f,(1.4f*mHeight+tw+2*th)/2.8f);
                    mTextPath.lineTo((1.4f*mHeight+tw+2*th)/2.8f,(1.4f*mHeight-tw+2*th)/2.8f);
                    canvas.drawTextOnPath(mText,mTextPath,0, 0, mPaint);
                    break;
            }
        }
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
        super.onSizeChanged(width, height, oldw, oldh);
        mWidth = width;
        mHeight = height;

        switch (mMode) {
            case TYPE_LEFT_TOP:
                mPointOne = new int[] { 0, 0 };
                mPointTwo = new int[] { width, 0 };
                mPointThree = new int[] { 0, height };
                break;
            case TYPE_RIGHT_BOTTOM:
                mPointOne = new int[] { width, 0 };
                mPointTwo = new int[] { width, height };
                mPointThree = new int[] { 0, height };
                break;
            case TYPE_RIGHT_TOP:
                mPointOne = new int[] { 0, 0 };
                mPointTwo = new int[] { width, 0 };
                mPointThree = new int[] { width, height };
                break;
            case TYPE_RIGHT_MIDDLE:
                mPointOne = new int[] { 0, 0 };
                mPointTwo = new int[] { width, height / 2 };
                mPointThree = new int[] { 0, height };
                break;
            case TYPE_LEFT_BOTTOM:
                mPointOne = new int[] { 0, 0 };
                mPointTwo = new int[] { width, height};
                mPointThree = new int[] { 0, height };
                break;
            case TYPE_RIGHT_BOTTOM_SMAILL:
                mPointOne = new int[] { width, 0 };
                mPointTwo = new int[] { width, height };
                mPointThree = new int[] { 0, height };
                break;
        }

        if (null != mPointOne) {
            mPath.moveTo(mPointOne[0], mPointOne[1]);
            mPath.lineTo(mPointTwo[0], mPointTwo[1]);
            mPath.lineTo(mPointThree[0], mPointThree[1]);
            mPath.close();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        RectF bounds = new RectF();
        mPath.computeBounds(bounds, true);
        Region region = new Region();
        region.setPath(mPath, new Region((int)bounds.left, (int)bounds.top,(int)bounds.right, (int)bounds.bottom));
        boolean ct =  region.contains((int)event.getX(), (int)event.getY());
        if(event.getAction() == MotionEvent.ACTION_DOWN){
            if(ct){
                isClicked = true;
                invalidate();
                return true;
            }
            return false;
        }else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL){
            if(isClicked){
                isClicked = false;
                invalidate();
                if (null != mOnClickListener && ct && event.getAction() != MotionEvent.ACTION_CANCEL) {
                    mOnClickListener.onClick(this);
                    return true;
                }
            }
            return false;
        }else if(event.getAction() == MotionEvent.ACTION_MOVE){
            return isClicked;
        }
        return super.onTouchEvent(event);
    }

    private OnClickListener mOnClickListener;
    public void setOnViewClickListener(OnClickListener clickListener){
        mOnClickListener = clickListener;
    }

    //计算两点的距离
    private int distance(PointF point1, PointF point2) {
        int disX = (int) Math.abs(point1.x - point2.x);
        int disY = (int) Math.abs(point1.y - point2.y);
        return (int) Math.sqrt(Math.pow(disX, 2) + Math.pow(disY, 2));
    }

    //将sp值转换为px值,保证文字大小不变
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }
}

以上三种方式的完整代码:https://github.com/gengqifu/Irregular-View


Android遮罩实现图片不规则的显示 我们在开发的过程中,经常遇到要对图片进行各种图形的变换,然而自己手动画图的话会显得格外的麻烦,所以我们想到了一个实现简单,容易理解的方法: 对图片进行遮罩,然后将重合的部分进行底部的显示或者顶部的显示等来进行我们的图片的修改。 拿我自己的例子来说一下吧,我这边是准备弄一个圆角的六边形的图片,网上也有很多是自己手动画的。下面我们说说遮罩的方法来实现: 代码: 首先是我们自定义的遮罩的方法,M 阅读详情

相关推荐

【高频考点精讲】CSS clip-path实战:用纯CSS实现各种不规则形状的UI设计

clip-path给了我们突破矩形限制的创造力。就像全栈老李常说的:"CSS不是限制,而是创意的画布"。下次设计师再丢给你一个奇怪形状的UI时,别急着拒绝,试试clip-path的魔力吧!记住,所有示例代码都可以在我的GitHub(github.com/quanzhanlaoli)找到完整demo。下期我们聊聊mask-image的妙用,敬请期待!

全栈老李的博客 1209

android下不规则多边形填充位图

最近研究android的2D图形,在android中,画多边形的方法很多,这里提供一个简单的方法。用Path来实现。Path path = new Path();path.moveTo(50, 50);path.lineTo(100, 50);path.lineTo(100, 0);path.lineTo(150, 0);path.lineTo(150, 50);path.lineTo(2

oathevil的专栏 7410

Android 实现不规则ImageView布局

不啰嗦,直接上代码!注释已经在代码里写的比较详细了!不懂的地方欢迎提问。 import java.io.IOException; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitm

追梦人的专栏 4115

安卓shape 画各种形状

画半圆 圆心为中点渐变色 <View android:id="@+id/gradient_view" android:layout_width="834dp" android:layout_height="417dp" android:layout_gravity="bottom|right" android:clickable="false" android:background="@drawable/gradient_bg_rot" />

shuangrenlong的博客 449

手把手教你用Simulink给C2000配置SCI串口发送数据(附模型与避坑点)

本文详细介绍了如何使用Simulink为C2000系列微控制器配置SCI串口发送数据,包括硬件资源分配、模型搭建、参数设置及调试技巧。通过代码生成和串口通信实践,帮助开发者高效实现嵌入式系统开发,避免常见通信问题。

weixin_30831529的博客 295

CST新手必看:共面波导色散曲线绘制全流程(附参数设置避坑指南)

本文详细介绍了CST Microwave Studio中绘制共面波导色散曲线的全流程操作,特别针对新手常见的边界条件设置错误、参数扫描失效等问题提供解决方案。通过模型建立、边界条件设置、本征模求解器配置等步骤,帮助工程师快速掌握CPW色散特性分析方法,并附有参数设置避坑指南,提升高频电路设计效率。

tomato的博客 988

Android使用shape绘制各种形状

在开发中经常会用到shape标签来定义控件的背景,好处是减少apk的占内存大小,shape标签总共有四个图形选项,分别是rectangle(矩形),oval(椭圆),line(横线)和ring(圆环)。 res下新建一个Drawable resource file: 矩形效果: &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;sha...

weixin_39654467的博客 7515

半导体设备微振动控制:天棚阻尼PID与SIMULINK建模实战

半导体制造设备对微振动的敏感度已进入纳米级,光刻机、量测设备等一旦遭遇超过VC-C振动准则的扰动,便会出现套刻误差或检测误判。主动隔振技术通过传感器、控制器与执行器形成闭环,能有效抑制低频共振,其中天棚阻尼控制因物理概念清晰、鲁棒性强而成为工程首选。结合MATLAB/SIMULINK建模,可在设计阶段完成系统性能边界评估与参数整定,再通过C代码生成与FMU导出实现算法向控制器和跨平台仿真迁移。本文从VC-C指标拆解出发,依次讲解天棚阻尼PID控制原理、两自由度被控对象建模、控制增益估算、仿真验证方法,并给出

weixin_29169505的博客 326

Android学习笔记——复杂应用组件

一、Handler机制(Android的消息队列机制) 1.Handler应用场景 启动今日头条app的时候,展示了一个开屏广告,默认播放x秒;在x秒后,需跳转到主界面。 用户在抖音App中,点击下载视频,下载过程中需要弹出Loading弹窗,下载结束后提示用户下载成功/失败。 Handler为Android系统解决以下两个问题: ①调度(Schedule)Android系统在某个时间点执行特定的任务,可以使用以下两种方式表示特定的任务: a. Message(android.os.Message

FengTingT的博客 1365

嘉立创EDA报错:元件引脚与焊盘未对应报错解决记录

原件引脚与焊盘未对应报错解决方法,一文总结!

2303_77013775的博客 1万+

MOSFET和IGBT栅极驱动器电路的基本原理学习(2)

1.PWM直接驱动 驱动主开关晶体管栅极的最简单方法是利用 PWM 控制器的栅极驱动输出,如图 (1)如 图 8中所示,PWM 控制器和 MOSFET 之间可能有较大距离。由于栅极驱动和接地环路迹线形成的环路,这个距离形成了寄生电感,从而降低了开关速度,并导致栅极驱动波形中形成振铃。为了降低与栅极驱动连接相关的电感,需要更宽的 PCB 迹线。 (2)直接栅极驱动的另一个问题是PWM 控制器的驱动电流能力有限。极少有集成电路能提供高于 1A 的峰值栅极驱动能力。 2.旁路电容的确定 (1)位置 如果是独立的

weixin_43732932的博客 5831

java 任意形状_0行代码实现任意形状图片展示--android-anyshape

前言在Android开发中, 我们经常会遇到一些场景, 需要以一些特殊的形状显示图片, 比如圆角矩形、圆形等等。关于如何绘制这类形状, 网上已经有很多的方案,比如自定义控件重写onDraw方法, 通过canvas的各种draw方法进行绘制等。那么, 更复杂的图形呢?比如,五角星?比如组合图形?又或者是各种奇奇怪怪的不规则图形呢?有同学会说, 如果已知不规则图形的具体形状, 那我们就可以通过连接顶点...

weixin_32349223的博客 257

基于 Python 的 LIF 模型:探索神经元同步与小世界网络

首先,我们定义了一个LIFNeuronself.soma.L = self.soma.diam = 12.6157 # 固定神经元形状self.cm = cm # 电容self.V_rest = V_rest # 静息电位self.V = V_rest # 初始化膜电位在初始化过程中,每个神经元被赋予初始的静息电位,并且其膜电位会在后续的模拟过程中动态变化。

qq_36224726的博客 1424

Android 实现不规则形状按钮

继承Button,通过重写onTouchEvent来达到目的。 package com.test; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.Button; public class

随便写写 1万+

【推荐100个unity插件之34】在unity中实现和Live2D虚拟人物的交互——Cubism SDK for Unity

Live2D是一款2D建模的软件,常用于游戏,动态壁纸,虚拟主播,游戏公司里面是有专门的ive2D岗位的。游戏里面还有一个经常使用的软件——spine,他做的也是2d的模型,但是他更适合做一些动作,Live2D更适合人物交互方面。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 4996

基于 ESP32 的分布式光伏微电网智能监控系统设计与实现

设备 / 模块连接 ESP32 引脚备注I2C 总线(SDA)GPIO21所有 I2C 设备共用I2C 总线(SCL)GPIO22所有 I2C 设备共用DS18B20(单总线)GPIO18单总线通信,需外接 4.7K 上拉电阻有源蜂鸣器GPIO19低电平触发,串联 100Ω 限流电阻ESP32 供电5V/GND由 LM2596 输出 5V 供电,或直接 USB 供电(调试阶段)INA219(光伏)光伏阵列正负极接 INA219 的 VIN+/VIN-

weixin_42300449的博客 304

android 用canvas.clipPath 画不规则的Bitmap

  转载请注明出处:http://www.bangchui.org/read.php?tid=10646通过使用canvas.clipPath,可以将一个规则的位图裁剪为不规则的位图一个矩形的图,一般画出来是这样子的:我们需要画成这个样子:通过canvas.clipPath(path);方法可以达到这个效果,看代码: package org.bangchui;   impor...

戒色 823

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Android 不规则按钮button

https://blog.youkuaiyun.com/parcool/article/details/40902203 效果图: 这个是我公司现在的一个小项目。我也不想去担心泄露机密什么的,我觉得有必要把这些东西放到网上大家共享一下。 需求:如图,在房子(整张是一个背景图片)的瓦片上蒙一层(这一层红色的东西后期会去掉,现在只是展示一个区域而已。正因为后面会去掉红色的这一层,那么就不能用图片透明的思路来做了...

快乐李同学的博客 1823

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

炫酷!这些都是Android中不规则形状View的布局实现!,不吃透都对不起自己

只有三种父布局是不是有点坑?万一我要用ConstraintLayout呢?那岂不是凉凉。没有ConstraintLayout这都被你发现了,由于ConstraintLayout并不存在于系统标准库中,而存在于支持库中,为了减少不必要的引用,让库拥有良好的独立性,故而没有实现(其实是因为懒…)。好了,其实也可以自己实现了,也是很简单的操作。自定义一个ClipPathLayout很简单,首先选择一个ViewGroup,然后实现ClipPathLayout接口。

m0_57472099的博客 1025

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

android一种不规则布局的实现方式

一.背景 正准备出去抽根烟的你突然被产品经理叫住了,“快来,快来,我告诉你一个好消息,前几天我不是说要给 app 的菜单入口这一截 UI 美化一下嘛,我昨天突发灵感,已经想好了一个很有特色的样式,这下我们的 UI 一定和别人的不一样,我们要让产品富有品牌表现力”。 这看起来是挺有特别的,不过感觉有违 android UI 排列常理呀,形状倒是没什么问题,关键是这无缝衔接就有点奇怪了,这形状最简单...

Aislli的博客 3672

Android开发之不规则图形按钮响应

这是我在网上找了不少质料后总结下来的简单不规则图形按钮设计方法。如果我们想在安卓开发时设计漂亮的按钮,很可能会用到图片按钮,但是图片按钮响应区域是长方形的,比如下图如果里面的月亮和云彩是用图片按钮制作,我们点击月亮中间空白部分,月亮按钮会响应,但是我们希望只有黄色部分是可以响应的。简单思路:把月亮中间空白部分设置为透明,通过判断点击点颜色是否透明的方法,考虑是否响应。设计思路:在ontouch中可...

qq_41910181的博客 1555

android不规则按钮,Android 实现不规则形状按钮

继承Button,通过重写onTouchEvent来达到目的。package com.test;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.widget.Button;public class IrregularButto...

weixin_42419782的博客 508

android 4个点矫正不规则矩形_使用 COMSOL 软件模拟不规则形状并构建几何模型示例...

本文是如何在 COMSOL Multiphysics®软件中模拟不规则形状的系列文章的第二部分。今天我们重点介绍如何基于以各种格式存储的高程数据(例如文本、图像或 DEM 文件)创建不规则形状的表面。此方法最适合高度(或高程)是x和y坐标函数的数据。一个不规则形状示例:欧洲山脉 Matterhorn在之前的文章中,我们使用人体头部作为不规则形状的示例,讨论了如何从导入的曲线数据...

weixin_39947521的博客 668

Android不规则点击区域详解

摘要 今天要和大家分享的是Android不规则点击区域,准确说是在视觉上不规则的图像点击响应区域分发。 其实这个问题比较简单,对于很多人来说根本不值得做为一篇博文写出来,但在我的群里确实有童鞋不了解如何实现并且很想知道完整的流程是什么样的,故完成demo作为参考。 以下篇幅记录个人分析流程。   我们的需求。   我们需要实现chrome这样的不同颜色区域点击响应不同的事件。Chro

npuhetao的专栏 726

Android 之 不规则形状图片

参考: Android不规则图形(1) Android 图片合成:添加蒙板效果 不规则相框 透明度渐变效果的实现

jfj545的专栏 762
上一篇: Python与设计模式(二)——builder
下一篇: google 官方ndk demo解析(二)——bitmap-plasma
hunter800421
博客等级 码龄22年 60粉丝 97原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值