温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2020-10-07 19:15:53 来源:脚本之家 阅读:264 作者:Moing557 栏目:移动开发

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

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

自定义View分四步。

一:自定义属性;
二:创建自定义View,在构造方法中拿到自定义属性;
三:重写onMeasure方法;
四:重写onDraw方法

先来个效果图

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

先在res/values/下新建attrs.xml
自定义属性

<declare-styleable name="RotateCircleImageView"> 
    <attr name="image" format="reference" /> 
    <attr name="rotate_sd" format="float" /> 
    <attr name="rotate_fx" format="integer" /> 
    <attr name="isRotate" format="boolean" /> 
    <attr name="circle_back_width" format="dimension" /> 
    <attr name="circle_back_color" format="color" /> 
  </declare-styleable> 

创建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; 
  } 

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

<?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" 
  android:fitsSystemWindows="true" 
  android:orientation="vertical"> 
 
<com.as.liji.jishiben.view.RotateCircleImageView 
    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" /> 
 
  <TextView 
    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" /> 
 
  <LinearLayout 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:layout_below="@id/tv" 
    android:orientation="horizontal"> 
 
    <Button 
      android:layout_width="0dp" 
      android:layout_height="wrap_content" 
      android:layout_weight="1" 
      android:onClick="startRotate" 
      android:text="开始" /> 
 
    <Button 
      android:layout_width="0dp" 
      android:layout_height="wrap_content" 
      android:layout_weight="1" 
      android:onClick="stopRotate" 
      android:text="暂停" /> 
  </LinearLayout> 
 
</RelativeLayout> 

在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(); 
  } 

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

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI