詳解Android自定義View--自定義柱狀圖
緒論
轉(zhuǎn)眼間,2016伴隨著互聯(lián)網(wǎng)寒冬和帝都的霧霾馬上就過(guò)去了,不知道大家今年一整年過(guò)得怎么樣?最近票圈被各個(gè)城市的霧霾刷屏,內(nèi)心難免會(huì)動(dòng)蕩,慶幸自己早出來(lái)一年,也擔(dān)憂著自己的未來(lái)的職業(yè)規(guī)劃。無(wú)所謂了,既然選擇了這個(gè)行業(yè),我覺(jué)得大家就應(yīng)該堅(jiān)持下去,路是自己走的,及時(shí)再寒冬,只要你足夠優(yōu)秀,足夠努力,相信你最后還是會(huì)找到自己滿意的工作的。最后還要感謝今年博客之星大家對(duì)我的投票支持,非常感謝。不多說(shuō)了,今天的主題是它–對(duì),自定義View柱狀圖。
先來(lái)說(shuō)說(shuō)我最近在做什么吧?好久沒(méi)有寫(xiě)博客了,最近手里有兩個(gè)項(xiàng)目,閑的時(shí)候一直在忙著做項(xiàng)目,也封裝了屬于自己的一套Library,抽下來(lái)我會(huì)把它分享出來(lái)的。公司的項(xiàng)目也一直在忙,今天的柱狀圖就是公司的項(xiàng)目所用到的。先來(lái)看一下效果吧
具體實(shí)現(xiàn)
可以看到,今天的柱狀圖分為三類(lèi):雙條豎向柱狀圖、單條豎向柱狀圖以及單條橫向柱狀圖,其實(shí)原理都是一樣的,下面我們具體看一下怎么實(shí)現(xiàn),怎么去畫(huà)一個(gè)這樣的柱狀圖。
雙條豎向
我們可以看到這個(gè)柱狀圖主要包括下面幾個(gè)方面:
- 雙條柱狀圖
- 橫坐標(biāo)月份
- 點(diǎn)擊tips顯示具體數(shù)值
- 灰色陰影(圖上沒(méi)有顯示具體看代碼)
- 柱狀圖漸變、圓角、點(diǎn)擊變色
好了上面五點(diǎn)就是需求和UI所提出來(lái)的所有東西,我們開(kāi)始著手去“畫(huà)”吧。
1.首先我們定義一些資源style供使用
包括
- leftColor 左側(cè)柱狀圖頂部顏色
- leftColorBottom 左側(cè)柱狀圖底部顏色
- rightColor 右側(cè)柱狀圖頂部顏色
- rightColorBottom 右側(cè)柱狀圖底部顏色
- selectRightColor 左側(cè)點(diǎn)擊選中顏色
- selectRightColor 右側(cè)點(diǎn)擊選中顏色
- xyColor 橫軸字體顏色
底部和頂部顏色是用于漸變用的
<declare-styleable name="MyChartView"> <attr name="leftColor" format="color"></attr> <attr name="leftColorBottom" format="color"></attr> <attr name="selectLeftColor" format="color"></attr> <attr name="rightColor" format="color"></attr> <attr name="rightColorBottom" format="color"></attr> <attr name="selectRightColor" format="color"></attr> <attr name="xyColor" format="color"></attr> </declare-styleable>
2.接下來(lái)我們看具體代碼,注釋寫(xiě)的很詳細(xì)了,仔細(xì)看:
- 初始化屬性、畫(huà)筆、所用的size等
- 測(cè)量計(jì)算高寬度等
- 畫(huà)坐標(biāo)軸、畫(huà)月份、畫(huà)柱狀圖、畫(huà)陰影
- 柱狀圖漸變以及點(diǎn)擊變色
- touch點(diǎn)擊事件判斷點(diǎn)擊所屬哪個(gè)月份,接口回調(diào)給activity顯示具體月份數(shù)值
注意:onWindowVisibilityChanged這個(gè)方法(當(dāng)屏幕焦點(diǎn)變化時(shí)重新側(cè)向起始位置,必須重寫(xiě)次方法,否則當(dāng)焦點(diǎn)變化時(shí)柱狀圖會(huì)跑到屏幕外面)
下面主要說(shuō)一下繪制部分吧
OnDraw()部分
我們將每次onTouch的條的索引放到selectIndexRoles數(shù)組中,然后當(dāng)這個(gè)數(shù)組包含該繪制的柱狀圖的索引是我們?cè)O(shè)置不用顏色以及不設(shè)置漸變;
同時(shí)我們給每?jī)蓚€(gè)雙條之間的的空白處繪制成陰影;
最后drawRoundRect()就繪制了一個(gè)圓角的矩形。
//畫(huà)柱狀圖 for (int i = 0; i < list.size(); i++) { int size = mHeight / 120; if (selectIndexRoles.contains(i)) { //偶數(shù) mChartPaint.setShader(null); if (i % 2 == 0) { mChartPaint.setColor(selectLeftColor); } else { mChartPaint.setColor(selectRightColor); } } else { //偶數(shù) if (i % 2 == 0) { LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100, (float) (mHeight - 100 - list.get(i) * size), lefrColorBottom, leftColor, Shader.TileMode.MIRROR); mChartPaint.setShader(lg); } else { LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100, (float) (mHeight - 100 - list.get(i) * size), rightColorBottom, rightColor, Shader.TileMode.MIRROR); mChartPaint.setShader(lg); } } mChartPaint.setStyle(Paint.Style.FILL); //畫(huà)陰影 if (i == number * 2 || i == number * 2 + 1) { mShadowPaint.setColor(Color.BLUE); } else { mShadowPaint.setColor(Color.WHITE); } //畫(huà)柱狀圖 RectF rectF = new RectF(); rectF.left = mChartWidth; rectF.right = mChartWidth + mSize; rectF.bottom = mHeight - 100; rectF.top = (float) (mHeight - 100 - list.get(i) * size); canvas.drawRoundRect(rectF, 10, 10, mChartPaint); //canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint) // ;// 長(zhǎng)方形 mChartWidth += (i % 2 == 0) ? (3 + getWidth() / 39) : (getWidth() / 13 - 3 - mSize); }
全部代碼
package com.hankkin.mycartdemo.chatview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import com.hankkin.mycartdemo.R; import java.util.ArrayList; import java.util.List; /** * Created by Hankkin on 2016/12/10. */ public class MyChartView extends View { private int leftColor;//雙柱左側(cè) private int rightColor;//雙柱右側(cè) private int lineColor;//橫軸線 private int selectLeftColor;//點(diǎn)擊選中左側(cè) private int selectRightColor;//點(diǎn)擊選中右側(cè) private int lefrColorBottom;//左側(cè)底部 private int rightColorBottom;//右側(cè)底部 private Paint mPaint, mChartPaint, mShadowPaint;//橫軸畫(huà)筆、柱狀圖畫(huà)筆、陰影畫(huà)筆 private int mWidth, mHeight, mStartWidth, mChartWidth, mSize;//屏幕寬度高度、柱狀圖起始位置、柱狀圖寬度 private Rect mBound; private List<Float> list = new ArrayList<>();//柱狀圖高度占比 private Rect rect;//柱狀圖矩形 private getNumberListener listener;//點(diǎn)擊接口 private int number = 1000;//柱狀圖最大值 private int selectIndex = -1;//點(diǎn)擊選中柱狀圖索引 private List<Integer> selectIndexRoles = new ArrayList<>(); public void setList(List<Float> list) { this.list = list; mSize = getWidth() / 39; mStartWidth = getWidth() / 13; mChartWidth = getWidth() / 13 - mSize - 3; invalidate(); } public void setListener(getNumberListener listener) { this.listener = listener; } public MyChartView(Context context) { this(context, null); } public MyChartView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyChartView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //獲取我們自定義的樣式屬性 TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView, defStyleAttr, 0); int n = array.getIndexCount(); for (int i = 0; i < n; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.MyChartView_leftColor: // 默認(rèn)顏色設(shè)置為黑色 leftColor = array.getColor(attr, Color.BLACK); break; case R.styleable.MyChartView_selectLeftColor: // 默認(rèn)顏色設(shè)置為黑色 selectLeftColor = array.getColor(attr, Color.BLACK); break; case R.styleable.MyChartView_rightColor: rightColor = array.getColor(attr, Color.BLACK); break; case R.styleable.MyChartView_selectRightColor: selectRightColor = array.getColor(attr, Color.BLACK); break; case R.styleable.MyChartView_xyColor: lineColor = array.getColor(attr, Color.BLACK); break; case R.styleable.MyChartView_leftColorBottom: lefrColorBottom = array.getColor(attr, Color.BLACK); break; case R.styleable.MyChartView_rightColorBottom: rightColorBottom = array.getColor(attr, Color.BLACK); break; } } array.recycle(); init(); } //初始化畫(huà)筆 private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); mBound = new Rect(); mChartPaint = new Paint(); mChartPaint.setAntiAlias(true); mShadowPaint = new Paint(); mShadowPaint.setAntiAlias(true); mShadowPaint.setColor(Color.WHITE); } //測(cè)量高寬度 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width; int height; int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { width = widthSize; } else { width = widthSize * 1 / 2; } if (heightMode == MeasureSpec.EXACTLY) { height = heightSize; } else { height = heightSize * 1 / 2; } setMeasuredDimension(width, height); } //計(jì)算高度寬度 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mWidth = getWidth(); mHeight = getHeight(); mStartWidth = getWidth() / 13; mSize = getWidth() / 39; mChartWidth = getWidth() / 13 - mSize; } //重寫(xiě)onDraw繪制 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(lineColor); //畫(huà)坐標(biāo)軸 //canvas.drawLine(0, mHeight - 100, mWidth, mHeight - 100, mPaint); for (int i = 0; i < 12; i++) { //畫(huà)刻度線 //canvas.drawLine(mStartWidth, mHeight - 100, mStartWidth, mHeight - 80, mPaint); //畫(huà)數(shù)字 mPaint.setTextSize(35); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.getTextBounds(String.valueOf(i + 1) + "", 0, String.valueOf(i).length(), mBound); canvas.drawText(String.valueOf(i + 1) + "月", mStartWidth - mBound.width() * 1 / 2, mHeight - 60 + mBound.height() * 1 / 2, mPaint); mStartWidth += getWidth() / 13; } //畫(huà)柱狀圖 for (int i = 0; i < list.size(); i++) { int size = mHeight / 120; if (selectIndexRoles.contains(i)) { //偶數(shù) mChartPaint.setShader(null); if (i % 2 == 0) { mChartPaint.setColor(selectLeftColor); } else { mChartPaint.setColor(selectRightColor); } } else { //偶數(shù) if (i % 2 == 0) { LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100, (float) (mHeight - 100 - list.get(i) * size), lefrColorBottom, leftColor, Shader.TileMode.MIRROR); mChartPaint.setShader(lg); } else { LinearGradient lg = new LinearGradient(mChartWidth, mChartWidth + mSize, mHeight - 100, (float) (mHeight - 100 - list.get(i) * size), rightColorBottom, rightColor, Shader.TileMode.MIRROR); mChartPaint.setShader(lg); } } mChartPaint.setStyle(Paint.Style.FILL); //畫(huà)陰影 if (i == number * 2 || i == number * 2 + 1) { mShadowPaint.setColor(Color.BLUE); } else { mShadowPaint.setColor(Color.WHITE); } //畫(huà)柱狀圖 RectF rectF = new RectF(); rectF.left = mChartWidth; rectF.right = mChartWidth + mSize; rectF.bottom = mHeight - 100; rectF.top = (float) (mHeight - 100 - list.get(i) * size); canvas.drawRoundRect(rectF, 10, 10, mChartPaint); //canvas.drawRect(mChartWidth, mHeight - 100 - list.get(i) * size, mChartWidth + mSize, mHeight - 100, mChartPaint) // ;// 長(zhǎng)方形 mChartWidth += (i % 2 == 0) ? (3 + getWidth() / 39) : (getWidth() / 13 - 3 - mSize); } } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus) { } } /** * 注意: * 當(dāng)屏幕焦點(diǎn)變化時(shí)重新側(cè)向起始位置,必須重寫(xiě)次方法,否則當(dāng)焦點(diǎn)變化時(shí)柱狀圖會(huì)跑到屏幕外面 */ @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); if (visibility == VISIBLE) { mSize = getWidth() / 39; mStartWidth = getWidth() / 13; mChartWidth = getWidth() / 13 - mSize - 3; } } /** * 柱狀圖touch事件 * 獲取觸摸位置計(jì)算屬于哪個(gè)月份的 * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); int y = (int) ev.getY(); int left = 0; int top = 0; int right = mWidth / 12; int bottom = mHeight - 100; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: for (int i = 0; i < 12; i++) { rect = new Rect(left, top, right, bottom); left += mWidth / 12; right += mWidth / 12; if (rect.contains(x, y)) { listener.getNumber(i, x, y); number = i; selectIndex = i; selectIndexRoles.clear(); ; selectIndexRoles.add(selectIndex * 2 + 1); selectIndexRoles.add(selectIndex * 2); invalidate(); } } break; case MotionEvent.ACTION_UP: break; } return true; } public interface getNumberListener { void getNumber(int number, int x, int y); } public int getLeftColor() { return leftColor; } public void setLeftColor(int leftColor) { this.leftColor = leftColor; } public int getRightColor() { return rightColor; } public void setRightColor(int rightColor) { this.rightColor = rightColor; } public int getLineColor() { return lineColor; } public void setLineColor(int lineColor) { this.lineColor = lineColor; } public int getSelectLeftColor() { return selectLeftColor; } public void setSelectLeftColor(int selectLeftColor) { this.selectLeftColor = selectLeftColor; } public int getSelectRightColor() { return selectRightColor; } public void setSelectRightColor(int selectRightColor) { this.selectRightColor = selectRightColor; } public int getLefrColorBottom() { return lefrColorBottom; } public void setLefrColorBottom(int lefrColorBottom) { this.lefrColorBottom = lefrColorBottom; } public int getRightColorBottom() { return rightColorBottom; } public void setRightColorBottom(int rightColorBottom) { this.rightColorBottom = rightColorBottom; } }
3.具體使用:
private void initChatView() { myChartView.setLefrColorBottom(getResources().getColor(R.color.leftColorBottom)); myChartView.setLeftColor(getResources().getColor(R.color.leftColor)); myChartView.setRightColor(getResources().getColor(R.color.rightColor)); myChartView.setRightColorBottom(getResources().getColor(R.color.rightBottomColor)); myChartView.setSelectLeftColor(getResources().getColor(R.color.selectLeftColor)); myChartView.setSelectRightColor(getResources().getColor(R.color.selectRightColor)); myChartView.setLineColor(getResources().getColor(R.color.xyColor)); chartList = new ArrayList<>(); relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout); relativeLayout.removeView(llChart); Random random = new Random(); while (chartList.size() < 24) { int randomInt = random.nextInt(100); chartList.add((float) randomInt); } myChartView.setList(chartList); myChartView.setListener(new MyChartView.getNumberListener() { @Override public void getNumber(int number, int x, int y) { relativeLayout.removeView(llChart); //反射加載點(diǎn)擊柱狀圖彈出布局 llChart = (LinearLayout) LayoutInflater.from(MainActivity.this).inflate(R.layout.layout_shouru_zhichu, null); TextView tvZhichu = (TextView) llChart.findViewById(R.id.tv_zhichu); TextView tvShouru = (TextView) llChart.findViewById(R.id.tv_shouru); tvZhichu.setText((number + 1) + "月支出" + " " + chartList.get(number * 2)); tvShouru.setText ( "收入: " + chartList.get(number * 2 + 1)); llChart.measure(0, 0);//調(diào)用該方法后才能獲取到布局的寬度 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params.leftMargin = x - 100; if (x - 100 < 0) { params.leftMargin = 0; } else if (x - 100 > relativeLayout.getWidth() - llChart.getMeasuredWidth()) { //設(shè)置布局距左側(cè)屏幕寬度減去布局寬度 params.leftMargin = relativeLayout.getWidth() - llChart.getMeasuredWidth(); } llChart.setLayoutParams(params); relativeLayout.addView(llChart); } }); }
經(jīng)過(guò)以上步驟,我們的雙條豎向柱狀圖就繪制完成了,也顯示出來(lái)了。其實(shí)自己坐下來(lái)仔細(xì)拿筆算一下,畫(huà)一下,也沒(méi)有想象的那么難。至于單條和橫向的實(shí)現(xiàn)原理都一樣,比這個(gè)要簡(jiǎn)單的多。哦對(duì)了,橫向的我只是自定義了一個(gè)橫向的柱狀圖View,然后用ListView顯示的各個(gè)部門(mén)的具體內(nèi)容。
代碼下載:demo
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)簡(jiǎn)易的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)易的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10Android獲取驗(yàn)證碼倒計(jì)時(shí)顯示效果
這篇文章主要為大家詳細(xì)介紹了Android獲取驗(yàn)證碼顯示的兩種簡(jiǎn)單實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Android List刪除重復(fù)數(shù)據(jù)
這篇文章主要介紹了Android List刪除重復(fù)數(shù)據(jù)的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-06-06Android自定義View實(shí)現(xiàn)水波紋效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)水波紋效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Android編程實(shí)現(xiàn)任務(wù)管理器的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)任務(wù)管理器的方法,涉及Android針對(duì)程序與進(jìn)程操作的相關(guān)技巧,需要的朋友可以參考下2015-12-12