android自定义view圆,Android自定义View实现旋转的圆形图片

自定义View是android开发的一个重要技能,用android提供的2/3D绘制相关类可以实现非常多炫酷的效果,需要实打实的编程基础。

但是自定义View又是我的弱项,所以最近都在摸索、练习自定义View。今天我写了一个圆形图片,同时不断匀速旋转的RotateCircleImageView。实现方法是自己想的,但肯定不是最好的实现方法。

自定义View分四步。

一:自定义属性;

二:创建自定义View,在构造方法中拿到自定义属性;

三:重写onMeasure方法;

四:重写onDraw方法

先来个效果图

663acba07ba17f7aa40d3c6828a008c3.gif

先在res/values/下新建attrs.xml

自定义属性

创建RotateCircleImageView

public RotateCircleImageView(Context context) {

this(context, null);

}

public RotateCircleImageView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public RotateCircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

initData();

}

重写View的三个构造函数,用一参的调用二参的,用二参的调用三参的。在三参的构造里初始化参数。

private Bitmap image;

private Bitmap tempImage;

private Paint paint;

private int bkWidth;//黑色圆边框的宽度

private int rotate_fx=0;//旋转方向 0=顺时针 1=逆时针

private float rotateSD = 0.8f;//每次旋转的角度--建议范围0.1f-1,否则会抖动

private boolean isRotate = false;//控制是否旋转

private void initData() {

paint = new Paint();

paint.setAntiAlias(true);

paint.setDither(true);

TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,

R.styleable.RotateCircleImageView, defStyleAttr, 0);//用这个类获得自定义的属性

paint.setColor(typedArray.getColor(R.styleable.RotateCircleImageView_circle_back_color,

Color.BLACK));

tempImage = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(

R.styleable.RotateCircleImageView_image, R.mipmap.ic_launcher));

bkWidth = typedArray.getDimensionPixelSize(R.styleable.

RotateCircleImageView_circle_back_width,

DensityUtils.dp2px(context, 100));//黑色边框的宽度,DensityUtils是我的一个工具类,将dp转换成px的

rotateSD = typedArray.getFloat(R.styleable.RotateCircleImageView_rotate_sd, 0.8f);

rotate_fx = typedArray.getInt(R.styleable.RotateCircleImageView_rotate_fx, 0);

isRotate = typedArray.getBoolean(R.styleable.RotateCircleImageView_isRotate, true);

}

重写测量方法:主要是测量包裹内容的情况下宽度和高度的值

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);//分别拿到宽高的大小和测量模式

int mWidth;//最终宽度

int mHeight;//最终高度

int yy_width = widthSize;//预测宽度,先假设它等于指定大小或填充窗体

if (widthMode == MeasureSpec.EXACTLY) {

mWidth = widthSize;//如果是指定大小或填充窗体(以后直接说成指定大小),直接设置最终宽度

} else {

yy_width=tempImage.getWidth();//如果是包裹内容,则预测宽度等于图片宽度

mWidth = yy_width + getPaddingLeft() + getPaddingRight();//最终宽度等于预测宽度加 左右Padding宽度

}

if (heightMode == MeasureSpec.EXACTLY) {

mHeight = heightSize;//同上

} else {

mHeight = getPaddingTop() + getPaddingBottom() + yy_width;//最终高度等于预测宽度加 上下Padding宽度

//目的是让控件的宽高相等,但Padding是可以由用户自由指定的,所以再加上padding

}

if (tempImage.getHeight() < tempImage.getWidth()) {

//这里用Bitmap类提供的缩放方法把图片缩放成指定大小,如果图片高度比宽度小,则强制拉伸

image = Bitmap.createScaledBitmap(tempImage, yy_width - bkWidth,

yy_width - bkWidth, false);

} else {

//这里用Bitmap类提供的缩放方法把图片缩放成指定大小(宽度等于预测的宽度,高度按比例缩放)

//该方法根据参数的宽高强制缩放图片,所以这里根据宽度算出缩放后的高度

image = Bitmap.createScaledBitmap(tempImage, yy_width - bkWidth,(int) (tempImage.getHeight() /

(((float) tempImage.getWidth()) / yy_width) - bkWidth), false);

}

setMeasuredDimension(mWidth, mHeight);//设置View的宽高,测量结束

}

假如宽度是指定大小,我希望高度根据这个大小按比例缩放,那么我需要拿到图片原始大小,所以需要一个tempImage,为什么写一个临时的Bitmap?因为我测试的时候发现   假如我用这个image直接把Bitmap.createScaledBitmap(image,xx,xx,false);的返回值赋给image的话,即使我在这行代码前去用image.getWidth()和Image.getHeight(),返回的值都已经变成缩放后的大小,而不是原始大小,这让我感到很奇怪。难道BItmap的getWidth和getHeight是异步的吗?希望知道的人帮我解答。

最后重写onDraw方法

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawCircle(getWidth() / 2, getWidth() / 2 , getWidth() / 2, paint);//绘制黑色圆

canvas.drawBitmap(getCircleBitmap(image, image.getWidth(), rotateSD),

getWidth() / 2 - image.getWidth() / 2,

getHeight() / 2 - image.getWidth() / 2, paint);//绘制圆形图片

if (isRotate) {

handler.postDelayed(runnable, 16);//16毫秒后启动子线程

}

}

getCircleBitmap方法和子线程的代码:

private Bitmap bitmap;

private boolean isCreateBitmap = false;

private Canvas canvas;

private PorterDuffXfermode pdf;

private Paint bitmapPaint;

private Bitmap getCircleBitmap(Bitmap image, int width, float rotate) {

if (!isCreateBitmap) {//节约资源所以这些代码只需要执行一次

bitmapPaint = new Paint();

bitmapPaint.setAntiAlias(true);//抗锯齿

bitmapPaint.setDither(true);//忘了是啥....反正效果好点

bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888);//创建一个指定宽高的空白bitmap

isCreateBitmap = true;

canvas = new Canvas(bitmap);//用那个空白bitmap创建一个画布

canvas.drawCircle(width / 2, width / 2, width / 2, bitmapPaint);//在画布上画个圆

pdf = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);//创建一个混合模式为保留后者相交的部分

}

bitmapPaint.setXfermode(pdf);//设置混合模式

if (rotate_fx==0) {

canvas.rotate(rotate, width / 2, width / 2);//顺时针

} else {//旋转画布:意思是下一次绘制的内容会被旋转这么多个角度

canvas.rotate(-rotate, width / 2, width / 2);//逆时针

}

canvas.drawBitmap(image, 0, 0, bitmapPaint);//绘制图片,(图片会被旋转)

bitmapPaint.setXfermode(null);

return bitmap;//这个bitmap在画布中被旋转,画圆,返回后就是一个圆形的bitmap

}

private Handler handler = new Handler();

private Runnable runnable = new Runnable() {

@Override

public void run() {

invalidate();//刷新界面

}

};

在第一次执行onDraw方法的时候得到的是一个旋转了0.8度的bitmap,然后16毫秒后启动子线程刷新,再次执行onDraw,得到一个再次旋转0.8度的bitmap,以此类推,所以不断旋转。想要转的快一点就把每次旋转的角度调大一点,但是不能太大,否则效果很不好。一卡一卡的。这样就完成了这个自定义view,非常简单,但是我却折腾了好久,主要还是测量的时候不够细心。实现方法都是自己整出来的,如果有更好的实现方法欢迎告知。

最后再暴露两个方法给外部

public void startRotate() {//开始旋转

if (!isRotate) {

this.isRotate = true;

invalidate();

}

}

public void stopRotate() {//暂停旋转

isRotate = false;

}

然后可以在布局里试试了:

xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:fitsSystemWindows="true"

android:orientation="vertical">

android:id="@+id/rcv"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_centerInParent="true"

app:circle_back_width="80dp"

app:image="@mipmap/sm"

app:isRotate="false"

app:rotate_fx="0"

app:rotate_sd="0.5" />

android:id="@+id/tv"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_below="@id/rcv"

android:layout_centerHorizontal="true"

android:ellipsize="marquee"

android:text="正在播放:蜘蛛侠插曲--Hold On" />

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_below="@id/tv"

android:orientation="horizontal">

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:onClick="startRotate"

android:text="开始" />

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:onClick="stopRotate"

android:text="暂停" />

在activity中拿到控件,重写两个按钮的点击事件方法:

private RotateCircleImageView rcv;

........onCreate(){

........

rcv = (RotateCircleImageView) findViewById(R.id.rcv);

}

public void startRotate(View v) {

rcv.startRotate();

}

public void stopRotate(View v) {

rcv.stopRotate();

}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值