前言:關於自定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();//刷新界面
}
}
}