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

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

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

自定义View分四步。

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

先来个效果图

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

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

(0)

相关推荐

  • Android裁剪图片为圆形图片的实现原理与代码

    以前在eoe论坛中找过裁剪图片为圆形图片的方法,但是效果都不是很理想,这几天因为公司业务的要求,需要对头像进行裁剪以圆形的方式显示,这个方法是根据传入的图片的高度(height)和宽度(width)决定的,如果是 width <= height时,则会裁剪高度,裁剪的区域是宽度不变高度从顶部到宽度width的长度:如果 width > height,则会裁剪宽度,裁剪的区域是高度不变,宽度是取的图片宽度的中心区域,不过不同的业务需求,对裁剪图片要求不一样,可以根据业务的需求来调整裁剪的区域.

  • Android使用自定义ImageView实现圆形图片效果

    android中的ImageView只能显示矩形的图片,这样一来不能满足我们其他的需求,比如要显示圆形的图片,这个时候,我们就需要自定义ImageView了,其原理就是首先获取到图片的Bitmap,然后进行裁剪圆形的bitmap,然后在onDraw()进行绘制圆形图片输出. 效果图如下: 自定义的圆形的ImageView类的实现代码如下: package com.xc.xcskin.view; import android.content.Context; import android.grap

  • Android编程绘制圆形图片的方法

    本文实例讲述了Android编程绘制圆形图片的方法.分享给大家供大家参考,具体如下: 效果图如下: 第一步:新建RoundView自定义控件继承View package com.rong.activity; import com.rong.test.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.grap

  • 详解Android中Glide与CircleImageView加载圆形图片的问题

    最近在项目中遇到了一个奇怪的问题,Glide和CircleImageView一起使用加载圆形头像,发现第一次死活都加载出来,出来的是一张占位图,当你刷新的时候或者第二次进入的时候才能加载出来.究其原因,CircleImageView 把位置占了.这时候我们有如下4种解决方案,不管是哪一种都是可以解决的(亲测可行). 1. 不使用占位符 注释掉这两句代码即可. .placeholder(R.drawable.normal_photo) .error(R.drawable.normal_photo)

  • Android自定义View旋转圆形图片

    一个自定义View,记录一下思路和代码以备以后使用. 思路: 1.首先要画一个圆形图片和一个圆形背景图(通过自定义View); 2.自定义View基本步骤初始化属性,测量宽高和中心点,然后绘制图片; 3.通过handler实现图片的角度旋转.然后然后就慢慢撸. 效果图: 1.废话不多直接上代码 public class MusicPlayerView extends View { private static final long ROTATE_DELAY = 5;//旋转动作时间 privat

  • android绘制圆形图片的两种方式示例

    android绘制圆形图片的两种方式 看下效果先 下面有完整的示例代码 使用BitmapShader(着色器) 我们在绘制view 的时候 就是小学上美术课 用水彩笔在本子上画画 使用着色器绘制圆形图片最简单的理解方式 就是把bitmap当做一种颜色 设置给paint ,paint都已经有颜色了 你想让它方了,圆了,扁了 还不是看你心情 canvas调用那个方法咯 实现的大致思路如下: 1. 创建一个类 继承imageView 重写onDraw() 2. 获取到bitmap图片 3. 计算图片的

  • Android中Glide加载圆形图片和圆角图片实例代码

    一.简介: 介绍两种使用 BitmapTransformation 来实现 Glide 加载圆形图片和圆角图片的方法.Glide 并不能直接支持 Round Pictures ,需要使用 BitmapTransformation 来进行处理. 二.网上的实现方式 这里介绍下网上常见的方式和使用 RoundedBitmapDrawable 两种方法,本质上是差不多的: 使用 Canvas 和 Paint 来绘制 使用 Android.support.v4.graphics.drawable.Rou

  • android自定义控件ImageView实现圆形图片

    android开发中常常涉及到一种情况,就是将用户上传的图片以圆形样式显示,但是用户上传的图片可以有直角.圆角.正方形等多种不确定样式,这时就用到了自定义ImageView控件,在安卓客户端使接收到的图片全部以圆形样式显示 CircleImageView.java public class CircleImageView extends ImageView { private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; p

  • Android开发实现圆形图片功能示例

    本文实例讲述了Android开发实现圆形图片功能.分享给大家供大家参考,具体如下: **绝对布局:通过直接给定控件起始坐标 ( x , y ) 和 ( w , l ) ,来生成控件. 圆形头像:CircleImageView的使用 ** 注:在build.gradle中添加: implementation 'de.hdodenhof:circleimageview:1.3.0' XML布局文件: <?xml version="1.0" encoding="utf-8&q

  • 分享一个Android设置圆形图片的特别方法

    Cardview配合ImageView显示圆形图效果图: 刚在看自定义View的知识点时,突然想起来,如果CardView宽高相等,CardView设置圆角的半径为宽高的一半时,不就是一个圆形嘛?! 1.布局文件 <android.support.v7.widget.CardView android:id="@+id/cv_img_activity" android:layout_width="200dp" android:layout_height=&quo

  • Android实现圆形图片或者圆角图片

    Android圆形图片或者圆角图片的快速实现,具体内容如下 话不多说直接上code xml文件布局 <LinearLayout android:id="@+id/ll_headpict" android:layout_width="match_parent" android:layout_height="97dp" android:layout_margin="13dp" android:background="

  • Android实现圆形图片的两种方式

    在项目中,我们经常会用到圆形图片,但是android本身又没有提供,那我只能我们自己来完成. 第一种方式,自定义CircleImageView: public class CircleImageView extends ImageView { private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Con

随机推荐