前言:关于自定view , 相信很多人都知道 自定义步骤 onMeasure-onLayout-onDraw , 但是自己能随手鲁出一个,相信很多人束手无措,记得很久以前在腾讯课堂**学院晚上听课, 讲了一个微信的雷达扫描, 当时觉得很难, 无法理解, 这几天一直在看View ViewGroup源码, 突然想起 顺手写了把, 和大家分享下。。。先看图
XML文件中简单布局
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000" >
<com.qypt.just_android_wechat_radar.Wechat_radar_view
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/radar"
/>
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:id="@+id/image"
android:layout_gravity="center"/>
</FrameLayout>
布局很简单,就一个FrameLayout 嵌套着自定义 雷达扫描布局,和一个头像,这里需要 头像压在自定布局上面, 因为跟上面的自定义布局的实现有关.详细请继续看
/**
* 定于全局Context
*/
private Context context;
/**
* 画笔, 用于画圆圈
*/
private Paint mPaint;
/**
* 头像的宽度
*/
private int width;
/**
* 头像的高度
*/
private int height;
/**
* 圆心的X,Y坐标
*/
private int pointX,pointY;
/**
* 最里层的圆半径, 最小那个圆的半径
*/
private int minRadius;
/**
* 圆与圆之间半径只差
*/
private static int ADD=60;
//圆的个数
private static int CIRCLE_NUMBER=5;
//矩阵 用于旋转圆
private Matrix mMatrix;
//旋转角度
private float degree=0;
<span style="white-space:pre"> </span>//控制渐变圆的绘制
private boolean isStart=true;
public Wechat_radar_view(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public Wechat_radar_view(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public Wechat_radar_view(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
init();
}
新建了一个类,命名为Wechat_radar_view extends View implements Runnable继承View 实现Runnable接口,定义变量 和构造方法,每一个变量都有注释了, 这里不多做解释, 构造方法,一般的做法都是,第一个调用第二个,第二个去调用第三个。。。 我也是从源码学来的;
看看init()吧, 看看究竟初始化了什么
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setDither(true);//设置抖动, 使画出来的东西更加平滑,清晰
mPaint.setStyle(Style.STROKE);//画笔为空心
mPaint.setColor(Color.parseColor("#CCA1A1A1"));//设置画笔颜色
this.setLayerType(LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
/**
* 获取半径
*/
TypedValue tv = new TypedValue();
width = (int) tv.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120,
context.getResources().getDisplayMetrics());
height = width; //这里宽高是头像的宽高
ADD=(int) tv.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60,
context.getResources().getDisplayMetrics()); //两圆之间的差 60dp 默认
if (width == 0 || width == -1) {
width = 240;
height = 240;
Log.i("Info", "obtain value fail");
}
minRadius=width/2;
tv = null;
int mScreenHeight=this.getResources().getDisplayMetrics().heightPixels;
CIRCLE_NUMBER=(mScreenHeight-width)/2/ADD+3; //计算圆的个数 , 这里多加两个原因是画满全屏看起来舒服写
mMatrix = new Matrix();
}
可以看到初始化onDraw()方法中所需要用的东西。 上面都是为onDraw方法准备东西, 既然准备好了, 我们就正式开始绘制我们的雷达扫描吧
<pre name="code" class="java">/**
* 绘制雷达
*/
@Override
protected void onDraw(Canvas canvas) {
int view_Width=this.getWidth(); //获取View的宽度
int view_Height=this.getHeight();//获取View的高度
pointX=view_Width/2;//圆心的X坐标
pointY=view_Height/2;//圆心的Y坐标
/**
* 一个For循环把所有的空心圆画出来
*/
for(int i=0;i<CIRCLE_NUMBER;i++)
{
canvas.drawCircle(pointX, pointY, minRadius+(i*ADD), mPaint);
}
//设置画笔颜色的渐变
Shader mShader = new SweepGradient(view_Width/2, view_Height / 2, Color.GRAY, Color.parseColor("#10FFFFFF"));
mPaint.setShader(mShader);
mPaint.setStyle(Style.FILL); //把画笔设置成实心
canvas.setMatrix(mMatrix); //设置矩阵
canvas.drawCircle(pointX, pointY, (minRadius+(CIRCLE_NUMBER*ADD)), mPaint); //画扫描圆
/**
* 恢复下画笔和重置矩阵
*/
mPaint.setShader(null);
mPaint.setStyle(Style.STROKE);
mMatrix.reset();
}
就这么简单就绘制完成我们的雷达扫描, 下面主要完成任务, 要控制他的动,和变化了,这里的动,主要通过不断的重绘我们的UI实现, 这里为了控制的视图的生命周期,用了 子线程, 而不是直接在onDraw方法 里调用this.invalidate()不断的递归回调,(也可以实现 不过不建议) 看看 Wechat_radar_view 的Run方法
@Override
public void run() {
while (isStart) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mMatrix.setRotate(--degree, this.getWidth()/2, this.getHeight()/2); //设置矩阵角度
postInvalidate();//刷新界面
}
}
先让线程睡100ms,不然转的太快, 然后设置矩阵,开始绘制; 就这么简单完成了我们自定义View, 其实这只是自定于View的冰山一角,因为自定View不单单这些, 有了它我们可以做出千变万化的View。今天我们的View很简单只重写了onDraw方法
/*******************************************解析完毕*************************************************/
因为代码不多, 就把所有代码贴出来了
/**
*
* @author Administrator justson
*
*/
public class MainActivity extends ActionBarActivity {
private Wechat_radar_view radar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
ImageView image=(ImageView) this.findViewById(R.id.image);
image.setImageBitmap(BitmapUtils.circleBitmap(BitmapUtils.getProcessBitmap(R.drawable.a, this, 120, 120)));
radar = (Wechat_radar_view) this.findViewById(R.id.radar);
}
@Override
protected void onResume() {
Thread mThread=new Thread(radar);
radar.setStart(true);
mThread.start();
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
radar.setStart(false);
}
}
下面的是图片处理类, 用来压缩图片和画圆角图片
/**
*
* @author Administrator justson
*
*/
public class BitmapUtils {
public static Bitmap getProcessBitmap(int resId,Context context, int width_dp,int height_dp)
{
if(resId==0||context==null)
return null;
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(context.getResources(), resId, options);
int pWidth=options.outWidth;
int pHeight=options.outHeight;
int rate=getRate(width_dp,height_dp,pWidth,pHeight,context);
options.inPurgeable=true;
options.inDither=true;
options.inJustDecodeBounds=false;
Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(), resId, options);
return bitmap;
}
public static Bitmap circleBitmap(Bitmap bitmap) {
if(bitmap==null)
{
return null;
}
int radius=Math.min(bitmap.getHeight(), bitmap.getWidth())/2;
Log.i("Info", "radius:"+radius);
Bitmap cBitmap=Bitmap.createBitmap(radius*2, radius*2, Config.ARGB_8888);
Canvas canvas =new Canvas(cBitmap);
RectF r=new RectF(0, 0, cBitmap.getWidth(), cBitmap.getHeight());
Paint mPaint=new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Style.FILL);
canvas.drawCircle(cBitmap.getWidth()/2, cBitmap.getHeight()/2 , radius, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, null, r, mPaint);
if(bitmap!=null){
bitmap.recycle();
bitmap=null;
}
return cBitmap;
}
private static int getRate(int width_dp, int height_dp, int pWidth,
int pHeight,Context context) {
int rate=1;
int cWidth=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width_dp, context.getResources().getDisplayMetrics());
int cHeight=(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height_dp, context.getResources().getDisplayMetrics());
if(cWidth>pWidth||cHeight>pHeight){
rate=Math.max(pWidth/cWidth, pHeight/cHeight);
}
return rate;
}
//自定义View类(雷达扫描View)
public class Wechat_radar_view extends View implements Runnable{
/**
* 定于全局Context
*/
private Context context;
/**
* 画笔, 用于画圆圈
*/
private Paint mPaint;
/**
* 头像的宽度
*/
private int width;
/**
* 头像的高度
*/
private int height;
/**
* 圆心的X,Y坐标
*/
private int pointX,pointY;
/**
* 最里层的圆半径, 最小那个圆的半径
*/
private int minRadius;
/**
* 圆与圆之间半径只差
*/
private static int ADD=60;
//圆的个数
private static int CIRCLE_NUMBER=5;
//矩阵 用于旋转圆
private Matrix mMatrix;
//旋转角度
private float degree=0;
private boolean isStart=true;
public Wechat_radar_view(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public Wechat_radar_view(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public Wechat_radar_view(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
init();
}
/**
* 初始化画笔
*/
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setDither(true);//设置抖动, 使画出来的东西更加平滑,清晰
mPaint.setStyle(Style.STROKE);//画笔为空心
mPaint.setColor(Color.parseColor("#CCA1A1A1"));//设置画笔颜色
this.setLayerType(LAYER_TYPE_SOFTWARE, null);//关闭硬件加速
/**
* 获取半径
*/
TypedValue tv = new TypedValue();
width = (int) tv.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120,
context.getResources().getDisplayMetrics());
height = width; //这里宽高是头像的宽高
ADD=(int) tv.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60,
context.getResources().getDisplayMetrics()); //两圆之间的差 60dp 默认
if (width == 0 || width == -1) {
width = 240;
height = 240;
Log.i("Info", "obtain value fail");
}
minRadius=width/2;
tv = null;
int mScreenHeight=this.getResources().getDisplayMetrics().heightPixels;
CIRCLE_NUMBER=(mScreenHeight-width)/2/ADD+3; //计算圆的个数 , 这里多加两个原因是画满全屏看起来舒服写
mMatrix = new Matrix();
}
/**
* 绘制雷达
*/
@Override
protected void onDraw(Canvas canvas) {
int view_Width=this.getWidth(); //获取View的宽度
int view_Height=this.getHeight();//获取View的高度
pointX=view_Width/2;//圆心的X坐标
pointY=view_Height/2;//圆心的Y坐标
/**
* 一个For循环把所有的空心圆画出来
*/
for(int i=0;i<CIRCLE_NUMBER;i++)
{
canvas.drawCircle(pointX, pointY, minRadius+(i*ADD), mPaint);
}
//设置画笔颜色的渐变
Shader mShader = new SweepGradient(view_Width/2, view_Height / 2, Color.GRAY, Color.parseColor("#10FFFFFF"));
mPaint.setShader(mShader);
mPaint.setStyle(Style.FILL); //把画笔设置成实心
canvas.setMatrix(mMatrix); //设置矩阵
canvas.drawCircle(pointX, pointY, (minRadius+(CIRCLE_NUMBER*ADD)), mPaint); //画扫描圆
/**
* 恢复下画笔和重置矩阵
*/
mPaint.setShader(null);
mPaint.setStyle(Style.STROKE);
mMatrix.reset();
}
public boolean isStart() {
return isStart;
}
public void setStart(boolean isStart) {
this.isStart = isStart;
}
@Override
public void run() {
while (isStart) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mMatrix.setRotate(--degree, this.getWidth()/2, this.getHeight()/2); //设置举证角度
postInvalidate();//刷新界面
}
}
}