Android 自定义View实现圆形头像(适用于任意布局)

23 篇文章 0 订阅

先看效果图:

先来说下我的思路:首先我需要在自定义View中动态获取头像id,那么就需要在attrs文件中,写一个关于该View类的自定义属性。这里仿照ImageView,取名为src,类型为reference引用类型

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="src" format="reference"/>
    </declare-styleable>
</resources>

然后在xml布局文件中,使用CircleImageView控件并加上这个“src”属性,表示我要通过src来获取图片引用

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
    <com.hualinfo.myviewtext.MyCircleImageView
        android:id="@+id/head"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:src="@mipmap/head"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="15dp"/>
</RelativeLayout>

接着,我们在CircleImageView类里调用该属性来获取图片

 //获取自定义属性
    private void getCustomType(){
        TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);
        headBitmap= BitmapFactory.decodeResource(getResources(),array.getResourceId(R.styleable.CircleImageView_src,0));
    }

R.styleable.CircleImageView_src:这是特定的书写模式,declare-styleable标签的name属性值_attr标签的name属性值

 通过以上步骤,我们就能动态获取到自己在布局页面中设置的图片。下面,我们来分析一下如何将图片变成圆形:

首先,canvas.drawCircle()方法是用来绘制一个圆的,但是传参时不能传入一张图片,也就是bitmap。了解过Shader着色器的应该知道,有一个类叫BitmapShader,它被创建的时候,需要传入一个bitmap,然后这个shader会被作为参数传给Paint画笔。这就是我们需要的,我们可以将上面动态获取到的bitmap作为参数传给Shader,Shader再传给Paint,最后在drawCircle()传入该Paint画笔,这样图片就能以圆形的样式显示出来了。具体代码如下:

public MyCircleImageView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    getCustomType(context,attrs);
    init();
}

private void init(){
    shader=new BitmapShader(headBitmap,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
    matrix=new Matrix();
    paint=new Paint();
    paint.setAntiAlias(true);
}

private void initPaintShader(){ //初始化画笔shader
    float scaleX=1,scaleY=1;
    //如果图片与圆的直径不一致,等比例缩放图片
    if(headBitmap.getWidth()!=radius*2||headBitmap.getHeight()!=radius*2){
        scaleX=(radius*2)/(headBitmap.getWidth()*1.0f);
        scaleY=(radius*2)/(headBitmap.getHeight()*1.0f);
    }
    matrix.setScale(scaleX,scaleY);
    shader.setLocalMatrix(matrix);
    paint.setShader(shader);
}

好了,通过以上方法,已经可以成功绘制出圆形头像。那么最后,我们要解决的问题是如何让自定义View可以在布局文件随意使用,我们需要适配控件中的padding属性,以及根据它的宽,高的测量模式,改变我们drawCircle时传入的半径radius参数。

1、重写onMeasure方法,动态调整radius圆形半径

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int modeX=MeasureSpec.getMode(widthMeasureSpec);
    int modeY=MeasureSpec.getMode(heightMeasureSpec);
    int sizeX=MeasureSpec.getSize(widthMeasureSpec);
    int sizeY=MeasureSpec.getSize(heightMeasureSpec);
    //width和height都是EXACTLY(精确)的测量模式
    if(modeX == MeasureSpec.EXACTLY && modeY == MeasureSpec.EXACTLY){
        radius=sizeX<sizeY?sizeX/2:sizeY/2;
    }else if(modeX == MeasureSpec.EXACTLY){
        radius=sizeX/2;
    }else if(modeY == MeasureSpec.EXACTLY){
        radius=sizeY/2;
    }
    //width和height都是AT_MOST(尽可以大)的测量模式,或者是UNSPECIAL(无上限)
    else{
        radius=headBitmap.getWidth()<headBitmap.getHeight()?headBitmap.getWidth()/2:headBitmap.getHeight()/2;
    }
    initPaintShader();
    //保存测量宽度和测量高度
    setMeasuredDimension(radius*2, radius*2);
}

2、在onDraw()中适配padding属性:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int realRadius=radius;
    //根据padding值,获取真实的圆的半径
    if(getPaddingLeft()+getPaddingRight()>=getPaddingTop()+getPaddingBottom())
        realRadius=(realRadius*2-getPaddingLeft()-getPaddingRight())/2;
    else
        realRadius=(realRadius*2-getPaddingTop()-getPaddingBottom())/2;
    //根据padding值,设置圆心的真实坐标
    canvas.drawCircle(radius+getPaddingLeft()-getPaddingRight(),radius+getPaddingTop()-getPaddingBottom(),realRadius,paint);
}

通过以上两步,就能够成功适配了。我们可以在布局文件中随意调整常用属性。

最后呈上完整代码:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;

public class MyCircleImageView extends View {

    private Bitmap headBitmap;  //头像图片
    private int radius;         //圆的半径
    private Paint paint;        //自定义画笔
    private Matrix matrix;
    private Shader shader;
    public MyCircleImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        getCustomType(context,attrs);
        init();
    }

    private void init(){
        shader=new BitmapShader(headBitmap,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
        matrix=new Matrix();
        paint=new Paint();
        paint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int modeX=MeasureSpec.getMode(widthMeasureSpec);
        int modeY=MeasureSpec.getMode(heightMeasureSpec);
        int sizeX=MeasureSpec.getSize(widthMeasureSpec);
        int sizeY=MeasureSpec.getSize(heightMeasureSpec);
        //width和height都是EXACTLY(精确)的测量模式
        if(modeX == MeasureSpec.EXACTLY && modeY == MeasureSpec.EXACTLY){
            radius=sizeX<sizeY?sizeX/2:sizeY/2;
        }else if(modeX == MeasureSpec.EXACTLY){
            radius=sizeX/2;
        }else if(modeY == MeasureSpec.EXACTLY){
            radius=sizeY/2;
        }
        //width和height都是AT_MOST(尽可以大)的测量模式,或者是UNSPECIAL(无上限)
        else{
            radius=headBitmap.getWidth()<headBitmap.getHeight()?headBitmap.getWidth()/2:headBitmap.getHeight()/2;
        }
        initPaintShader();
        //保存测量宽度和测量高度
        setMeasuredDimension(radius*2, radius*2);
    }

    private void initPaintShader(){ //初始化画笔shader
        float scaleX=1,scaleY=1;
        //如果图片与圆的直径不一致,等比例缩放图片
        if(headBitmap.getWidth()!=radius*2||headBitmap.getHeight()!=radius*2){
            scaleX=(radius*2)/(headBitmap.getWidth()*1.0f);
            scaleY=(radius*2)/(headBitmap.getHeight()*1.0f);
        }
        matrix.setScale(scaleX,scaleY);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
    }

    //获取自定义属性
    private void getCustomType(Context context,AttributeSet attrs){
        TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.CircleImageView);
        headBitmap= BitmapFactory.decodeResource(getResources(),array.getResourceId(R.styleable.CircleImageView_src,0));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int realRadius=radius;
        //根据padding值,获取真实的圆的半径
        if(getPaddingLeft()+getPaddingRight()>=getPaddingTop()+getPaddingBottom())
            realRadius=(realRadius*2-getPaddingLeft()-getPaddingRight())/2;
        else
            realRadius=(realRadius*2-getPaddingTop()-getPaddingBottom())/2;
        //根据padding值,设置圆心的真实坐标
        canvas.drawCircle(radius+getPaddingLeft()-getPaddingRight(),radius+getPaddingTop()-getPaddingBottom(),realRadius,paint);
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实现倒计时圆形进度,你可以创建一个自定义View,使用 Canvas 和 Paint 来绘制圆形和进度条。以下是一个示例代码: ```java public class CountdownCircleView extends View { private int maxProgress = 100; // 总进度 private int progress = 100; // 当前进度 private int circleColor = Color.GRAY; // 圆形颜色 private int progressColor = Color.BLUE; // 进度条颜色 private Paint circlePaint; private Paint progressPaint; public CountdownCircleView(Context context) { super(context); init(); } public CountdownCircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public CountdownCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setColor(circleColor); progressPaint = new Paint(); progressPaint.setAntiAlias(true); progressPaint.setColor(progressColor); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int viewWidth = getWidth(); int viewHeight = getHeight(); // 绘制圆形 int diameter = Math.min(viewWidth, viewHeight); int radius = diameter / 2; int centerX = viewWidth / 2; int centerY = viewHeight / 2; canvas.drawCircle(centerX, centerY, radius, circlePaint); // 绘制进度条 RectF rectF = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius); float sweepAngle = 360f * progress / maxProgress; canvas.drawArc(rectF, -90, sweepAngle, true, progressPaint); } public void setMaxProgress(int maxProgress) { this.maxProgress = maxProgress; } public void setProgress(int progress) { this.progress = progress; invalidate(); // 重新绘制 } } ``` 你可以将上述代码放入你的 Android 项目中,并在布局文件中使用 `CountdownCircleView`。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值