【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】
1 背景
前面分析那麼多系統源碼了,也該暫停下來休息一下,趁昨晚閒着看見一個有意思的需求就操練一下分析源碼後的實例演練—-自定義控件。
這個實例很適合新手入門自定義控件。先看下效果圖:
橫屏模式如下:
豎屏模式如下:
看見沒有,這個控件完全自定義的,連文字等都是自定義的,沒有任何圖片等資源,就僅僅是一個小的java文件,這個界面只有一個控件。如下咱們看下實現代碼。
【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】
2 實例代碼
如下就是整個工程的源碼了。
自定義上面展示的控件AreaChartsView源碼:
/**
* Author : yanbo
* Date : 2015-06-03
* Time : 09:22
* Description : 自定義區域描述圖表View
*/
public class AreaChartsView extends View {
private Paint mPaint;
private int[] mZeroPos = new int[2];
private int[] mMaxYPos = new int[2];
private int[] mMaxXPos = new int[2];
private int mWidth, mHight;
private int mRealWidth, mRealHight;
private String mTitleY, mTitleX;
private ArrayList<Integer> mXLevel = new ArrayList<>();
private ArrayList<Integer> mYLevel = new ArrayList<>();
private ArrayList<String> mGridLevelText = new ArrayList<>();
private ArrayList<Integer> mGridColorLevel = new ArrayList<>();
private ArrayList<Integer> mGridTxtColorLevel = new ArrayList<>();
private int mGridLevel = mXLevel.size() - 1;
//title字符大小
private int mXYTitleTextSize = 40;
private int mMeasureXpos, mMeasureYpos;
public AreaChartsView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mPaint.setFilterBitmap(true);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mWidth = getWidth();
mHight = getHeight();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initPosition();
drawXYTitle(canvas);
drawXYLine(canvas);
drawContent(canvas);
}
private void initPosition() {
//初始化座標圖的xy交點原點座標
mZeroPos[0] = mXYTitleTextSize * 2;
mZeroPos[1] = mHight - mXYTitleTextSize * 4;
//初始化座標圖的X軸最大值座標
mMaxXPos[0] = mWidth;
mMaxXPos[1] = mHight - mXYTitleTextSize * 4;
//初始化座標圖的Y軸最大值座標
mMaxYPos[0] = mXYTitleTextSize * 2;
mMaxYPos[1] = mXYTitleTextSize * 2;
}
private void drawXYTitle(Canvas canvas) {
mPaint.setColor(Color.parseColor("#1FB0E7"));
mPaint.setTextSize(mXYTitleTextSize);
mPaint.setTextAlign(Paint.Align.LEFT);
//畫Y軸頂的title
canvas.drawText(mTitleY, mMaxYPos[0] - mXYTitleTextSize * 2, mMaxYPos[1] - mXYTitleTextSize, mPaint);
mPaint.setTextAlign(Paint.Align.RIGHT);
//畫X軸頂的title
canvas.drawText(mTitleX, mMaxXPos[0], mMaxXPos[1] + mXYTitleTextSize * 2, mPaint);
}
private void drawXYLine(Canvas canvas) {
mPaint.setColor(Color.DKGRAY);
mPaint.setTextAlign(Paint.Align.RIGHT);
//畫XY軸
canvas.drawLine(mMaxYPos[0], mMaxYPos[1], mZeroPos[0], mZeroPos[1], mPaint);
canvas.drawLine(mZeroPos[0], mZeroPos[1], mMaxXPos[0], mMaxXPos[1], mPaint);
}
private void drawContent(Canvas canvas) {
mGridLevel = mXLevel.size() - 1;
//計算出偏移title等顯示尺標後的真實XY軸長度,便於接下來等分
mRealWidth = (mWidth - mXYTitleTextSize * 2);
mRealHight = (mHight - mXYTitleTextSize * 4);
//算出等分間距
int offsetX = mRealWidth/(mGridLevel);
int offsetY = mRealHight/(mGridLevel+1);
//循環繪製content
for (int index=0; index<mGridLevel+1; index++) {
mPaint.setColor(Color.DKGRAY);
mPaint.setTextAlign(Paint.Align.RIGHT);
mPaint.setTextSize(mXYTitleTextSize-5);
//繪製X軸的那些座標區間點,包含0點座標
canvas.drawText(String.valueOf(mXLevel.get(index)), mZeroPos[0]+(index*offsetX), mZeroPos[1] + mXYTitleTextSize, mPaint);
if (index != 0) {
//繪製Y軸座標區間點,不包含0點座標,X軸已經畫過了
canvas.drawText(String.valueOf(mYLevel.get(index)), mZeroPos[0], mZeroPos[1]-(index*offsetY), mPaint);
}
if (index == mGridLevel) {
//座標區間 = 真實區間 + 1
break;
}
mPaint.setColor(mGridColorLevel.get(mGridLevel - 1 - index));
mPaint.setStyle(Paint.Style.FILL);
//繪製區間疊加圖譜方塊,從遠到0座標,因爲小的圖會覆蓋大的圖
canvas.drawRect(mMaxYPos[0], mMaxYPos[1] + index*offsetY, mMaxXPos[0]-index*offsetX, mMaxXPos[1], mPaint);
mPaint.setColor(mGridTxtColorLevel.get(index));
mPaint.setTextAlign(Paint.Align.RIGHT);
mPaint.setTextSize(mXYTitleTextSize);
//繪製每個方塊狀態區間的提示文字
canvas.drawText(mGridLevelText.get(index), mMaxXPos[0] - index * offsetX - mXYTitleTextSize,
mMaxYPos[1] + index * offsetY + mXYTitleTextSize, mPaint);
}
//繪製當前座標
drawNotice(canvas, offsetX, offsetY);
}
private void drawNotice(Canvas canvas, int offsetX, int offsetY) {
int realPosX = 0;
int realPosY = 0;
//計算傳入的x值與真實屏幕座標的像素值的百分比差值轉換
for (int index=0; index<mGridLevel; index++) {
if (mMeasureXpos >= mXLevel.get(index) && mMeasureXpos < mXLevel.get(index+1)) {
int subValue = mMeasureXpos - mXLevel.get(index);
int offset = mXLevel.get(index+1) - mXLevel.get(index);
realPosX = mZeroPos[0] + index*offsetX + (subValue / offset);
break;
}
}
//計算傳入的y值與真實屏幕座標的像素值的百分比差值轉換
for (int index=0; index<mGridLevel; index++) {
if (mMeasureYpos >= mYLevel.get(index) && mMeasureYpos < mYLevel.get(index+1)) {
int subValue = mMeasureYpos - mYLevel.get(index);
int offset = mYLevel.get(index+1) - mYLevel.get(index);
realPosY = mZeroPos[1] - index*offsetY - (offsetY - (subValue / offset));
break;
}
}
//畫我們傳入的座標點的標記小紅點
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(realPosX, realPosY, 8, mPaint);
int[] centerPos = {mZeroPos[0] + mRealWidth/2, mZeroPos[1] - mRealHight/2};
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
RectF rectF = null;
Path path = new Path();
//畫紅點旁邊的提示框和文字,有四個區域,然後提示框的小三角指標方位不同
if (realPosX <= centerPos[0] && realPosY >= centerPos[1]) {
//left-bottom
//畫三角形
path.moveTo(realPosX+5, realPosY+5);
path.lineTo(realPosX+15, realPosY+15);
path.lineTo(realPosX+15, realPosY-15);
//畫矩形背景
rectF = new RectF(realPosX+15, realPosY-40, realPosX+200, realPosY + 30);
canvas.drawRoundRect(rectF, 15, 15, mPaint);
//畫提示框的文字
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setTextSize(mXYTitleTextSize - 5);
canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX+30, realPosY, mPaint);
}
else if (realPosX <= centerPos[0] && realPosY < centerPos[1]) {
//left-top
path.moveTo(realPosX+5, realPosY+5);
path.lineTo(realPosX+15, realPosY+15);
path.lineTo(realPosX + 15, realPosY - 15);
rectF = new RectF(realPosX+15, realPosY - 20, realPosX+200, realPosY + 50);
canvas.drawRoundRect(rectF, 15, 15, mPaint);
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setTextSize(mXYTitleTextSize - 5);
canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX+30, realPosY+20, mPaint);
}
else if (realPosX > centerPos[0] && realPosY >= centerPos[1]) {
//right-bottom
path.moveTo(realPosX-5, realPosY+5);
path.lineTo(realPosX-15, realPosY+15);
path.lineTo(realPosX - 15, realPosY - 15);
rectF = new RectF(realPosX-200, realPosY-40, realPosX-15, realPosY + 30);
canvas.drawRoundRect(rectF, 15, 15, mPaint);
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setTextSize(mXYTitleTextSize - 5);
canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX-180, realPosY, mPaint);
}
else if (realPosX > centerPos[0] && realPosY < centerPos[1]) {
//right-top
path.moveTo(realPosX-5, realPosY+5);
path.lineTo(realPosX-15, realPosY+15);
path.lineTo(realPosX - 15, realPosY - 15);
rectF = new RectF(realPosX-200, realPosY - 20, realPosX-15, realPosY + 50);
canvas.drawRoundRect(rectF, 15, 15, mPaint);
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setTextSize(mXYTitleTextSize - 5);
canvas.drawText("("+mMeasureXpos+", "+mMeasureYpos+")", realPosX-180, realPosY+30, mPaint);
}
path.close();
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawPath(path, mPaint);
}
//設置當前比值
public void updateValues(int x, int y) {
mMeasureXpos = x;
mMeasureYpos = y;
postInvalidate();
}
//設置XY軸頂角的title字體大小
public void setTitleTextSize(int size) {
mXYTitleTextSize = size;
}
//初始化X軸的座標區間點值,可以不均等分
public void initXLevelOffset(ArrayList<Integer> list) {
mXLevel.clear();
mXLevel.addAll(list);
}
//初始化Y軸的座標區間點值,可以不均等分
public void initYLevelOffset(ArrayList<Integer> list) {
mYLevel.clear();
mYLevel.addAll(list);
}
//初始化每個區間的提示文字,如果不想顯示可以設置""
public void initGridLevelText(ArrayList<String> list) {
mGridLevelText.clear();
mGridLevelText.addAll(list);
}
//初始化每個區間的顏色
public void initGridColorLevel(ArrayList<Integer> list) {
mGridColorLevel.clear();
mGridColorLevel.addAll(list);
}
//初始化每個區間的提示文字顏色
public void initGridTxtColorLevel(ArrayList<Integer> list) {
mGridTxtColorLevel.clear();
mGridTxtColorLevel.addAll(list);
}
//初始化XY軸title
public void initTitleXY(String x, String y) {
mTitleX = x;
mTitleY = y;
}
}
再來看下佈局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yanbober.customerviewdemo.areachartsview.AreaChartsView
android:id="@+id/area_charts_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"/>
</RelativeLayout>
再看看主界面:
public class MainActivity extends AppCompatActivity {
private AreaChartsView mAreaChartsView;
private Timer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAreaChartsView = (AreaChartsView) this.findViewById(R.id.area_charts_view);
//初始化自定義圖表的規格和屬性
ArrayList<Integer> mXLevel = new ArrayList<>();
ArrayList<Integer> mYLevel = new ArrayList<>();
ArrayList<String> mGridLevelText = new ArrayList<>();
ArrayList<Integer> mGridColorLevel = new ArrayList<>();
ArrayList<Integer> mGridTxtColorLevel = new ArrayList<>();
//初始化x軸座標區間
mXLevel.add(0);
mXLevel.add(60);
mXLevel.add(90);
mXLevel.add(100);
mXLevel.add(110);
mXLevel.add(120);
//初始化y軸座標區間
mYLevel.add(0);
mYLevel.add(90);
mYLevel.add(140);
mYLevel.add(160);
mYLevel.add(180);
mYLevel.add(200);
//初始化區間顏色
mGridColorLevel.add(Color.parseColor("#1FB0E7"));
mGridColorLevel.add(Color.parseColor("#4FC7F4"));
mGridColorLevel.add(Color.parseColor("#4FDDF2"));
mGridColorLevel.add(Color.parseColor("#90E9F4"));
mGridColorLevel.add(Color.parseColor("#B2F6F1"));
//初始化區間文字提示顏色
mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
mGridTxtColorLevel.add(Color.parseColor("#EA8868"));
mGridTxtColorLevel.add(Color.WHITE);
mGridTxtColorLevel.add(Color.BLACK);
//初始化區間文字
mGridLevelText.add("異常");
mGridLevelText.add("過高");
mGridLevelText.add("偏高");
mGridLevelText.add("正常");
mGridLevelText.add("偏低");
mAreaChartsView.initGridColorLevel(mGridColorLevel);
mAreaChartsView.initGridLevelText(mGridLevelText);
mAreaChartsView.initGridTxtColorLevel(mGridTxtColorLevel);
mAreaChartsView.initXLevelOffset(mXLevel);
mAreaChartsView.initYLevelOffset(mYLevel);
mAreaChartsView.initTitleXY("投入量(H)", "產出量(H)");
}
@Override
protected void onStart() {
super.onStart();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Random random = new Random();
int x = random.nextInt(120) % (120 + 1) + 0;
Random randomy = new Random();
int y = randomy.nextInt(200) % (200 + 1) + 0;
//隨機模擬賦值
mAreaChartsView.updateValues(x, y);
}
}, 0, 1000);
}
@Override
protected void onPause() {
super.onPause();
timer.cancel();
}
}
【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】
3 總結
上面代碼很簡單,核心的都已經註釋了,不需要過多解釋。核心思路就是一些座標點的計算。該控件支持設置mergin及width與hight等屬性,支持自定義所有顏色及顯示及座標區分等,唯一缺陷就是沒來得及寫attr屬性xml設置這些值,有興趣的自己實現吧,我是沒時間了。
可以發現,自定義View無非就是重寫前面文章分析的那三個方法而已。
重點只提供實現思路,具體細節沒時間優化。有需求的可以在下面討論。
【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】