自定义View——聊天页图片展示

我尽量不打错别字,用词准确,不造成阅读障碍。

本文是自定义聊天时的图片和视频显示及发送控件。

效果:

在这里插入图片描述 在这里插入图片描述

代码:

自定义属性

 <declare-styleable name="MyIMImageView">
        <attr name="arrowTop" format="dimension|reference" />
        <attr name="arrowWidth" format="dimension|reference" />
        <attr name="arrowHeight" format="dimension|reference" />
        <attr name="circular" format="dimension|reference" />
        <attr name="direction" format="string" />
        <attr name="showText" format="boolean" />
        <attr name="showShadow" format="boolean" />
    </declare-styleable>

View代码

public class MyIMImageView extends android.support.v7.widget.AppCompatImageView {
    private float mAngleLength = dp2px(20);      //圆弧半径
    private float mArrowTop = dp2px(40);         //箭头距离顶部的位置,有的需要箭头在正中间
    private float mArrowWidth = dp2px(20);       //箭头宽度
    private float mArrowHeight = dp2px(20);      //箭头高度

    private Paint mBitmapPaint;          //画Bitmap画笔
    private Paint mTextPaint;            //写进度的Bitmap
    private String direction;            //方向

    private Bitmap mBitmap;              //资源图片
    private BitmapShader mBitmapShader;  //bitmap着色器

    private RectF mBitmapRectF;
    private RectF mShadowRectF = new RectF();
    private Rect mTextRect = new Rect();
    private Path mPath;

    private boolean isShowText;    //是否显示文字
    private boolean isShowShadow;  //是否显示阴影
    private int mProgressPercent = 69;

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

    public MyIMImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAttr(attrs);
    }

    private void initAttr(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MyIMImageView);
        mArrowTop = typedArray.getDimension(R.styleable.MyIMImageView_arrowTop, mArrowTop);
        mArrowWidth = typedArray.getDimension(R.styleable.MyIMImageView_arrowWidth, mArrowWidth);
        mArrowHeight = typedArray.getDimension(R.styleable.MyIMImageView_arrowHeight, mArrowHeight);
        mAngleLength = typedArray.getDimension(R.styleable.MyIMImageView_circular, mAngleLength);
        direction = typedArray.getString(R.styleable.MyIMImageView_direction);
        isShowText = typedArray.getBoolean(R.styleable.MyIMImageView_showText, false);
        isShowShadow = typedArray.getBoolean(R.styleable.MyIMImageView_showShadow, false);
        typedArray.recycle();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBitmapPaint != null) {
            canvas.drawPath(mPath, mBitmapPaint);
            drawShadowAndProgress(canvas, mBitmapRectF);  //画阴影和进度
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mBitmapRectF = new RectF(getPaddingLeft(), getPaddingTop(), getRight()
                - getLeft() - getPaddingRight(), getBottom() - getTop()
                - getPaddingBottom());
        mPath = new Path();
        setBitmap();
        if ("right".equals(direction)) {
            rightPath(mBitmapRectF, mPath);
        } else if ("left".equals(direction)) {
            leftPath(mBitmapRectF, mPath);
        }
    }

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

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

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
    }
  
  //三角形在有右边
    private void rightPath(RectF rectF, Path path) {
        path.moveTo(rectF.left + mAngleLength, rectF.top);   //path移动到上边直线左点
        path.lineTo(rectF.right - mAngleLength - mArrowWidth, rectF.top);  //划上边线
        //画右上角弧线
        path.arcTo(new RectF(rectF.right - mAngleLength - mArrowWidth, rectF.top, rectF.right - mArrowWidth, mAngleLength + rectF.top), 270, 90);
        path.lineTo(rectF.right - mArrowWidth, rectF.top + mArrowTop);//画弧线和三角之间的直线
        path.lineTo(rectF.right, rectF.top + mArrowTop + mArrowHeight / 2);                           //画三角的上边线
        path.lineTo(rectF.right - mArrowWidth, rectF.top + mArrowTop + mArrowHeight);      //画三角的下边线
        path.lineTo(rectF.right - mArrowWidth, rectF.bottom - mAngleLength);//三角下边的线
        //画右下角弧线
        path.arcTo(new RectF(rectF.right - mAngleLength - mArrowWidth, rectF.bottom - mAngleLength, rectF.right - mArrowWidth, rectF.bottom), 0, 90);
        path.lineTo(rectF.left - mAngleLength, rectF.bottom);  //画底边线
        //画左下角弧线
        path.arcTo(new RectF(rectF.left, rectF.bottom - mAngleLength, mAngleLength + rectF.left, rectF.bottom), 90, 90);
        path.lineTo(rectF.left, rectF.top - mAngleLength);     //画左边线
        //画左上角弧线
        path.arcTo(new RectF(rectF.left, rectF.top, mAngleLength + rectF.left, mAngleLength + rectF.top), 180, 90);
        path.close();
    }

    //三角形在左边
    private void leftPath(RectF rectF, Path path) {
        path.moveTo(rectF.left + mAngleLength + mArrowWidth, rectF.top);//path移动到上边直线左点
        path.lineTo(rectF.right - mAngleLength, rectF.top);  //划上边线
        //画右上角弧线
        path.arcTo(new RectF(rectF.right - mAngleLength, rectF.top, rectF.right, mAngleLength + rectF.top), 270, 90);
        path.lineTo(rectF.right, rectF.bottom - mAngleLength);
        //画右下角弧线
        path.arcTo(new RectF(rectF.right - mAngleLength, rectF.bottom - mAngleLength, rectF.right, rectF.bottom), 0, 90);
        path.lineTo(rectF.left - mAngleLength - mArrowWidth, rectF.bottom);  //画底边线
        //画左下角弧线
        path.arcTo(new RectF(rectF.left + mArrowWidth, rectF.bottom - mAngleLength, mAngleLength + rectF.left + mArrowWidth, rectF.bottom), 90, 90);
        path.lineTo(rectF.left + mArrowWidth, rectF.top + mArrowTop + mArrowHeight);       //三角下边的线
        path.lineTo(rectF.left, rectF.top + mArrowTop + mArrowHeight / 2);                   //画三角的下边线
        path.lineTo(rectF.left + mArrowWidth, rectF.top + mArrowTop);      //画三角的上边线
        path.lineTo(rectF.left + mArrowWidth, rectF.top + mArrowTop);   //三角上边的线
        //画左上角弧线
        path.arcTo(new RectF(rectF.left + mArrowWidth, rectF.top, mAngleLength + rectF.left + mArrowWidth, mAngleLength + rectF.top), 180, 90);
        path.close();
    }
  
  private void drawShadowAndProgress(Canvas canvas, RectF rectF) {
        if (isShowShadow) {
            mTextPaint.setAntiAlias(true);
            mTextPaint.setStyle(Paint.Style.FILL);
            mTextPaint.setColor(Color.parseColor("#70000000"));
            if ("right".equals(direction)) {
                mShadowRectF.set(rectF.left, rectF.top, rectF.right - mArrowWidth, rectF.bottom);
            } else {
                mShadowRectF.set(rectF.left + mArrowWidth, rectF.top, rectF.right, rectF.bottom);
            }
            canvas.drawRoundRect(mShadowRectF, mAngleLength / 2, mAngleLength / 2, mTextPaint);
        }
        if (isShowText) {
            mTextPaint.setColor(Color.WHITE);
            mTextPaint.setTextSize(getResources().getDimension(R.dimen.shibadp));
            mTextPaint.getTextBounds(mProgressPercent + "%", 0, (mProgressPercent + "%").length(), mTextRect);
            if ("right".equals(direction)) {
                canvas.drawText(mProgressPercent + "%", (rectF.right - mArrowWidth - mTextRect.width()) / 2, rectF.bottom / 2, mTextPaint);
            } else {
                canvas.drawText(mProgressPercent + "%", (rectF.left + rectF.right - mTextRect.width()) / 2, rectF.bottom / 2, mTextPaint);
            }
        }
    }
   
    //从Drawable中获取bitmap
    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap;
        if (drawable instanceof ColorDrawable) {
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
        } else {
            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;
    }

    private void setBitmap() {
        mTextPaint = new Paint();
        //设置着色器为拉伸,图片很小的时候可以用
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());
    }
  
   public void setProgressPercent(int percent) {
        this.mProgressPercent = percent;
        postInvalidate();
    }

    public void setIsShowText(boolean isShowText) {
        this.isShowText = isShowText;
    }

    public void setIsShowShadow(boolean isShowShadow) {
        this.isShowShadow = isShowShadow;
    }
}

原理很简单的,就是设置图片后先获取Bitmap,然后设置shader和paint,然后画图形的path,最后在onDraw方法里,根据paint和path来画图。

Bug

但是这时候有bug,就是图片大小和矩形大小不一致,图片需要处理,基本99%的图片都需要处理,比如图片太大,而我们的控件小,那么控件只能显示图片中,左上角位置控件大小的部分,其他部分放不下,就这样:新建位图图像

红色矩形代表图片,黑色部分为控件,虚线部分都看不见。解决方法也很简单,就是缩放,这里我们一般使用矩阵进行缩放操作,写在setBitmap()里。

代码如下:

private void setBitmap() {
        mTextPaint = new Paint();
        //设置着色器为拉伸
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);

        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
        setMatrix();
    }

setMatrix()方法:

private void setMatrix() {
        float scaleX;
        float scaleY;
        Matrix mShaderMatrix = new Matrix(); //矩阵对象
        mShaderMatrix.set(null);             //设置成单位矩阵
        scaleY = (mBitmapRectF.bottom - mBitmapRectF.top) / mBitmapHeight; //Y轴缩放比例
        scaleX = (mBitmapRectF.right - mBitmapRectF.left) / mBitmapWidth;  //X轴缩放比例
        mShaderMatrix.setScale(scaleX, scaleY);  //缩放
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

到这里,理论是没问题了,但是实际是欠考虑了一个问题,如果你设置了padding属性,比如我设置了paddingStart和paddingTop,结果是这样的:
111
原因很简单,矩阵缩放是以左上角为原点进行缩放的,缩放完了以后还是以左上角为原点,所以与我们控件可显示区域对不上,解决也简单,移动一下就好了:

private void setMatrix() {
        float scaleX;
        float scaleY;
        float dx;
        float dy;

        Matrix mShaderMatrix = new Matrix();
        mShaderMatrix.set(null);
        scaleY = (mBitmapRectF.bottom - mBitmapRectF.top) / mBitmapHeight;
        dx = mBitmapRectF.left;
        scaleX = (mBitmapRectF.right - mBitmapRectF.left) / mBitmapWidth;
        dy = mBitmapRectF.top;
        mShaderMatrix.setScale(scaleX, scaleY);
        mShaderMatrix.postTranslate(dx, dy);      //移动
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

最后结果是这样:

222
完美!其实我感觉矩阵应该是可以设置缩放后的原点的,就是省去移动那步,但是我现在对矩阵还不是很了解,后期尝试着更改。同样的,在这个基础之上可以做视频发送的控件,将drawText()换成draw其他图形就好了。

集合了一些简单自定义View的github地址:
https://github.com/longlong-2l/MySelfViewDemo
很简单,没有太多高深的用法,适合学习入门。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值