当前位置: 移动技术网 > 移动技术>移动开发>Android > Android仿微信、QQ附近好友雷达扫描效果

Android仿微信、QQ附近好友雷达扫描效果

2019年07月24日  | 移动技术网移动技术  | 我要评论

1.概述

  最近一直到在带实习生,因为人比较多,所以很长一段时间没有更新博客了,今天更新一篇雷达扫描附近好友效果,以后尽量每周更新一篇,先看一下效果:

2.实现 

1、效果分析

效果分为两个部分,一个是上半部分的自定义radarview,还有就是下半部分的viewpager,至于怎么做到缩放和背景虚化的效果大家可以去看看lazyviewpager这里不详细介绍,这里主要实现扫描效果部分。

2、扫描效果实现

2.1自定义radarview在ondraw()方法中画六个圆圈,至于圆圈的半径是多少我们需要通过onmeasure(int widthmeasurespec, int heightmeasurespec)测量方法获取控件的宽高来确定圆的半径,每个圆的半径是宽度的1 / 13f, 2 / 13f, 3 / 13f, 4 / 13f, 5 / 13f, 6 / 13f,这只是自己测试出来感觉比较舒适的效果,下面请看代码:

//每个圆圈所占的比例
private static float[] circleproportion = {1 / 13f, 2 / 13f, 3 / 13f, 4 / 13f, 5 / 13f, 6 / 13f};
private paint mpaintcircle;//画圆需要用到的paint

public class radarview extends view {
 public radarview(context context) {
 this(context, null);
 }

 public radarview(context context, attributeset attrs) {
 this(context, attrs, 0);
 }

 public radarview(context context, attributeset attrs, int defstyleattr) {
 super(context, attrs, defstyleattr);
 init();
 }


 private void init() {
 mpaintcircle = new paint();
 mpaintcircle.setcolor(color.white);
 mpaintcircle.setantialias(true);
 }

 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 // 获取控件的宽高
 setmeasureddimension(measuresize(widthmeasurespec), measuresize(widthmeasurespec));
 mwidth = getmeasuredwidth();
 mheight = getmeasuredheight();
 mwidth = mheight = math.min(mwidth, mheight);
 }

 @override
 protected void ondraw(canvas canvas) {
 // 绘制六个白色圆圈
 drawcircle(canvas);
 }

 /**
 * 绘制圆线圈
 *
 * @param canvas
 */
 private void drawcircle(canvas canvas) {
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[1], mpaintline); // 绘制最小圆
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[1], mpaintline); // 绘制小圆
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[2], mpaintline); // 绘制中圆
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[3], mpaintline); // 绘制中大圆
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[4], mpaintline); // 绘制大圆
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[5], mpaintline); // 绘制大大圆
 }
}

2.2下面需要去画中间的用户图像,可以运行看看中间的六个圆圈有没有达到效果,这里就不看了直接在ondraw()方法中画中间图像:

 private bitmap centerbitmap;//最中间icon

 private void init(){
 // 通过bitmap工厂区获取用户图像的bitmap
 centerbitmap = bitmapfactory.decoderesource(getresources(), r.drawable.circle_photo);
 }

 @override
 protected void ondraw(canvas canvas) {
 drawcentericon(canvas);
 }

 /**
 * 绘制最中间的图标
 *
 * @param canvas
 */
 private void drawcentericon(canvas canvas) {
 int iconwidth = mwidth * circleproportion[0];
 canvas.drawbitmap(centerbitmap, 0,0,iconwidth ,iconwidth , null);
 }

2.3最后只需要实现这个扫描的效果这个控件基本就完成了,第一需要开启线程不断调用invalidate()去更新ondraw()方法,第二需要熟悉扫描渲染sweepgradient这个类,如果这两个都没问题那么大功告成:

private paint mpaintscan;//画扫描需要用到的paint
private matrix matrix = new matrix();//旋转需要的矩阵
private int mrotedegree;//扫描旋转的角度
private shader scanshader;//扫描渲染shader

public runnable run = new runnable() {
 @override
 public void run() {
  mrotedegree +=2;
  mrotematrix.postrotate(mrotedegree,cx,cy);
  invalidate();
  postdelayed(run,60);
 }
 };

@override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 //设置扫描渲染的shader
 scanshader = new sweepgradient(mwidth / 2, mheight / 2,
  new int[]{color.transparent, color.parsecolor("#84b5ca")}, null);
 }

 @override
 protected void ondraw(canvas canvas) {
 drawscan(canvas);
 }

 /**
 * 绘制扫描
 *
 * @param canvas
 */
 private void drawscan(canvas canvas) {
 canvas.save();
 mpaintscan.setshader(scanshader);
 canvas.concat(matrix);
 canvas.drawcircle(mwidth / 2, mheight / 2, mwidth * circleproportion[4], mpaintscan);
 canvas.restore();
 }


2.4.到这里我们来看一下扫描radarview的效果

3. 实现添加数据效果radarviewgroup,我们的图像附近点需要加入viewgroup这里又需要自定义了,这里简单说一下自定viewgroup:
1).onmeasure()测量方法这里就不说了
2).只要搞清楚onlayout()方法是干嘛的就ok,viewgroup里面的子view都显示在什么位置就是写在这个方法里面的,换句话说有的隔得近有的隔得远都是由 child.layout(int l, int t, int r, int b)决定的,下面我们看一下代码:

 public class radarviewgroup extends viewgroup implements radarview.iscanninglistener {
 private int mwidth, mheight;//viewgroup的宽高
 private sparsearray<float> scananglelist = new sparsearray<>();//记录展示的item所在的扫描位置角度
 private sparsearray<info> mdatas;//数据源
 private int datalength;//数据源长度
 private int minitemposition;//最小距离的item所在数据源中的位置
 private circleview currentshowchild;//当前展示的item
 private circleview minshowchild;//最小距离的item
 private iradarclicklistener iradarclicklistener;//雷达图中点击监听circleview小圆点回调接口

 public void setiradarclicklistener(iradarclicklistener iradarclicklistener) {
 this.iradarclicklistener = iradarclicklistener;
 }

 public radarviewgroup(context context) {
 this(context, null);
 }

 public radarviewgroup(context context, attributeset attrs) {
 this(context, attrs, 0);
 }

 public radarviewgroup(context context, attributeset attrs, int defstyleattr) {
 super(context, attrs, defstyleattr);
 }


 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 setmeasureddimension(measuresize(widthmeasurespec), measuresize(heightmeasurespec));
 mwidth = getmeasuredwidth();
 mheight = getmeasuredheight();
 mwidth = mheight = math.min(mwidth, mheight);
 //测量每个children
 measurechildren(widthmeasurespec, heightmeasurespec);
 for (int i = 0; i < getchildcount(); i++) {
  view child = getchildat(i);
  if (child.getid() == r.id.id_scan_circle) {
  //为雷达扫描图设置需要的属性
  ((radarview) child).setscanninglistener(this);
  //考虑到数据没有添加前扫描图在扫描,但是不会开始为circleview布局
  if (mdatas != null && mdatas.size() > 0) {
   ((radarview) child).setmaxscanitemcount(mdatas.size());
   ((radarview) child).startscan();
  }
  continue;
  }
 }
 }

 @override
 protected void onlayout(boolean changed, int l, int t, int r, int b) {
 int childcount = getchildcount();
 //首先放置雷达扫描图
 view view = findviewbyid(r.id.id_scan_circle);
 if (view != null) {
  view.layout(0, 0, view.getmeasuredwidth(), view.getmeasuredheight());
 }
 //放置雷达图中需要展示的item圆点
 for (int i = 0; i < childcount; i++) {
  final int j = i;
  final view child = getchildat(i);
  if (child.getid() == r.id.id_scan_circle) {
  //如果不是circleview跳过
  continue;
  }
  //设置circleview小圆点的坐标信息
  //坐标 = 旋转角度 * 半径 * 根据远近距离的不同计算得到的应该占的半径比例
  ((circleview) child).setdisx((float) math.cos(math.toradians(scananglelist.get(i - 1) - 5))
   * ((circleview) child).getproportion() * mwidth / 2);
  ((circleview) child).setdisy((float) math.sin(math.toradians(scananglelist.get(i - 1) - 5))
   * ((circleview) child).getproportion() * mwidth / 2);
  //如果扫描角度记录sparsearray中的对应的item的值为0,
  // 说明还没有扫描到该item,跳过对该item的layout
  //(scananglelist设置数据时全部设置的value=0,
  // 当onscanning时,value设置的值始终不会0,具体可以看onscanning中的实现)
  if (scananglelist.get(i - 1) == 0) {
  continue;
  }
  //放置circle小圆点
  child.layout((int) ((circleview) child).getdisx() + mwidth / 2, (int) ((circleview) child).getdisy() + mheight / 2,
   (int) ((circleview) child).getdisx() + child.getmeasuredwidth() + mwidth / 2,
   (int) ((circleview) child).getdisy() + child.getmeasuredheight() + mheight / 2);
  //设置点击事件
  child.setonclicklistener(new onclicklistener() {
  @override
  public void onclick(view v) {
   resetanim(currentshowchild);
   currentshowchild = (circleview) child;
   //因为雷达图是childat(0),所以这里需要作-1才是正确的circle
   startanim(currentshowchild, j - 1);
   if (iradarclicklistener != null) {
   iradarclicklistener.onradaritemclick(j - 1);

   }
  }
  });
 }


 }

 private int measuresize(int measurespec) {
 int result = 0;
 int specmode = measurespec.getmode(measurespec);
 int specsize = measurespec.getsize(measurespec);
 if (specmode == measurespec.exactly) {
  result = specsize;
 } else {
  result = 300;
  if (specmode == measurespec.at_most) {
  result = math.min(result, specsize);
  }
 }
 return result;

 }

 /**
 * 设置数据
 *
 * @param mdatas
 */
 public void setdatas(sparsearray<info> mdatas) {
 this.mdatas = mdatas;
 datalength = mdatas.size();
 float min = float.max_value;
 float max = float.min_value;
 //找到距离的最大值,最小值对应的minitemposition
 for (int j = 0; j < datalength; j++) {
  info item = mdatas.get(j);
  if (item.getdistance() < min) {
  min = item.getdistance();
  minitemposition = j;
  }
  if (item.getdistance() > max) {
  max = item.getdistance();
  }
  scananglelist.put(j, 0f);
 }
 //根据数据源信息动态添加circleview
 for (int i = 0; i < datalength; i++) {
  circleview circleview = new circleview(getcontext());
  if (mdatas.get(i).getsex()) {
  circleview.setpaintcolor(getresources().getcolor(r.color.bg_color_pink));
  } else {
  circleview.setpaintcolor(getresources().getcolor(r.color.bg_color_blue));
  }
  //根据远近距离的不同计算得到的应该占的半径比例 0.312-0.832
  circleview.setproportion((mdatas.get(i).getdistance() / max + 0.6f) * 0.52f);
  if (minitemposition == i) {
  minshowchild = circleview;
  }
  addview(circleview);
 }
 }

 /**
 * 雷达图没有扫描完毕时回调
 *
 * @param position
 * @param scanangle
 */
 @override
 public void onscanning(int position, float scanangle) {
 if (scanangle == 0) {
  scananglelist.put(position, 1f);
 } else {
  scananglelist.put(position, scanangle);
 }
 requestlayout();
 }

 /**
 * 雷达图扫描完毕时回调
 */
 @override
 public void onscansuccess() {
 logutil.m("完成回调");
 resetanim(currentshowchild);
 currentshowchild = minshowchild;
 startanim(currentshowchild, minitemposition);
 }

 /**
 * 恢复circleview小圆点原大小
 *
 * @param object
 */
 private void resetanim(circleview object) {
 if (object != null) {
  object.clearportaiticon();
  objectanimator.offloat(object, "scalex", 1f).setduration(300).start();
  objectanimator.offloat(object, "scaley", 1f).setduration(300).start();
 }

 }

 /**
 * 放大circleview小圆点大小
 *
 * @param object
 * @param position
 */
 private void startanim(circleview object, int position) {
 if (object != null) {
  object.setportraiticon(mdatas.get(position).getportraitid());
  objectanimator.offloat(object, "scalex", 2f).setduration(300).start();
  objectanimator.offloat(object, "scaley", 2f).setduration(300).start();
 }
 }

 /**
 * 雷达图中点击监听circleview小圆点回调接口
 */
 public interface iradarclicklistener {
 void onradaritemclick(int position);
 }

 /**
 * 根据position,放大指定的circleview小圆点
 *
 * @param position
 */
 public void setcurrentshowitem(int position) {
 circleview child = (circleview) getchildat(position + 1);
 resetanim(currentshowchild);
 currentshowchild = child;
 startanim(currentshowchild, position);
 }
}

源码下载:

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网