Android(安卓)项目(一):自定义View绘制“折线图”
都说不要造轮子不要造轮子,可是我还是在不停的造轮子啊!!!闲话不多说了看看上面折线图的效果(Ps:当然了如果有现成的例子或者是开源的东西最好还是参考别人的或者是开源的,毕竟自己自定义view好看是好看但是太耗费时间很多东西需要自己去慢慢调试)。
如果你有了一定的android基础,建议在绘制上面的折线图之前还是仔细观察一下,有了自己的思路再来看下面的博客。
通过上面的效果图可以看出画布的底层是白色的背景,之后绘制了一个灰色的矩形区域+绘制网格背景(说白了就是画线),然后就是折线图的具体绘制了,关于折线图的具体绘制从效果上看是先绘制的线再绘制的两层的小圆点(一个底部蓝色的圆点+一个表层的白色圆点)然后就是折线所围区域的阴影部分;这里简单说一下折线就是通过画线绘制的,圆点画圆,阴影部分通过path路径绘制(给画笔透明度);绘制的时候为了确定折线的绘制路线可以先绘制圆点再绘制折线,最后绘制阴影
好了,上面就是绘制折线图的大体思路了
一、创建自定义view
这里就是先创建一个class来继承View,然后再在canvas画布上进行绘制,先不要管怎么绘制,具体绘制下面会给出。
ps:
1、代码private List
中自己创建的一个类,为了添加数据方便,如果不是为了以后整个项目考虑,可以自己在该class中随意添加数据,之后在MainActivity中就不需要再添加数据了。
2、有使用到heigh的地方需要在onMeasure方法中使用,不然会是0;比如gridspace_heigh(每个网格的高度)
/** * Created by Administrator on 2015/10/13. */public class BrokenLineCusVisitView extends View { private int width; private int heigh; //网格的宽度与高度 private int gridspace_width; private int gridspace_heigh; //底部空白的高度 private int brokenline_bottom; //灰色背景的画笔 private Paint mPaint_bg; //灰色网格的画笔 private Paint mPaint_gridline; //文本数据的画笔 private Paint mPaint_text; //折线圆点的蓝色背景 private Paint mPaint_point_bg; //折线圆点的白色表面 private Paint mPaint_point_sur; //阴影路径的画笔 private Paint mPaint_path; //折线的画笔 private Paint mPaint_brokenline; //路径 private Path mpath=new Path(); //客户拜访的折线(BrokenLineCusVisit)数据 private List mdata; public BrokenLineCusVisitView(Context context) { super(context); } public BrokenLineCusVisitView(Context context, AttributeSet attrs) { super(context, attrs); inite(context); } private void inite(Context context) { mPaint_bg=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_bg.setColor(Color.argb(0xff,0xef,0xef,0xef)); mPaint_gridline=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_gridline.setColor(Color.argb(0xff,0xce,0xCB,0xce)); mPaint_brokenline=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_brokenline.setColor(Color.argb(0xff,0x91,0xC8,0xD6)); mPaint_brokenline.setTextSize(18); mPaint_brokenline.setTextAlign(Paint.Align.CENTER); mPaint_point_bg=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_point_bg.setColor(Color.argb(0xff, 0x91, 0xC8, 0xD6)); //注意path的画笔的透明度已经改变了 mPaint_path=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_path.setColor(Color.argb(0x33,0x91,0xC8,0xD6)); mPaint_point_sur=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_point_sur.setColor(Color.WHITE); mPaint_text=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_text.setColor(Color.BLACK); mPaint_text.setTextAlign(Paint.Align.CENTER); invalidate(); } //data的set/get方法,用于设置数据 public List getMdata() { return mdata; } public void setMdata(List mdata) { this.mdata = mdata; requestLayout(); invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制白色背景 canvas.drawColor(Color.WHITE); //绘制灰色矩形区域 canvas.drawRect(10,0,width,heigh-brokenline_bottom,mPaint_bg); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); gridspace_width= 50; if(mdata.size()==0){ width=getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec); } else{ //根据数据的条数设置宽度 width=gridspace_width*mdata.size()+10; } heigh=getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec); brokenline_bottom=50; Log.d("bottm",""+brokenline_bottom); gridspace_heigh=(heigh-brokenline_bottom)/4; setMeasuredDimension(width, heigh); }}
二、Dao模式创建数据类
看上面的图可以发现它需要有数据,这里采用Dao模式创建客户拜访的折线类(折线BrokenLine),向其中添加数据。
Dao模式的折线类
创建类,给出属性,给出属性的set/get方法,给出构造器
package dao;/** * Created by Administrator on 2015/10/13. */public class BrokenLineCusVisit { //拜访日期 private String date; //拜访数量 private int data; public BrokenLineCusVisit(String date, int data) { this.date = date; this.data = data; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public int getData() { return data; } public void setData(int data) { this.data = data; }}
Xml文件中引用自定义View(包名+类名)
将自定义的view放入HorzontalView中使自定义view能够水平滑动
ps:android:scrollbars="none"
设置水平滚动条的滚动条隐藏
<LinearLayout 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" android:orientation="vertical" tools:context=".MainActivity"><HorizontalScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none" > <widget.chart.BrokenLineCusVisitView android:id="@+id/brokenline" android:layout_width="match_parent" android:layout_height="match_parent" />HorizontalScrollView>LinearLayout>
MainActivity中为自定义View添加数据
找到自定义view的id,通过自定义view的set/get方法设置它的data数据
public class MainActivity extends BaseActivity { private List mdata=new ArrayList<>(); private BrokenLineCusVisitView brokenline; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); brokenline= (BrokenLineCusVisitView) findViewById(R.id.brokenline); for (int i = 0; i <30 ; i++) { BrokenLineCusVisit brokenline=new BrokenLineCusVisit(i+"",i+1); mdata.add(brokenline); } brokenline.setMdata(mdata); } }
三、绘制折线图
绘制背景(一、从简单开始)
白色背景+灰色矩形+网格+圆点+数据文本
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制折线图的白色背景 canvas.drawColor(Color.WHITE); //绘制灰色矩形区域 canvas.drawRect(10,0,width,heigh-brokenline_bottom,mPaint_bg); //绘制网格线,横向的;Y轴不变 X轴绘制直线 for (int j=0;j<4;j++){ canvas.drawLine(10,gridspace_heigh*(j+1),width,gridspace_heigh*(j+1),mPaint_gridline); } for (int i= 0; i < mdata.size();i++){ //绘制纵向网格线 canvas.drawLine(gridspace_width * i + 10, 0, gridspace_width * i + 10, heigh - brokenline_bottom, mPaint_gridline); //绘制圆点,圆点位置根据网格线的位置确定 canvas.drawCircle(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, 10, mPaint_point_bg); canvas.drawCircle(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, 5, mPaint_point_sur); //绘制数据的数量 String data=mdata.get(i).getData()+""; canvas.drawText(data,gridspace_width*i+10,heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData()/100-mPaint_brokenline.measureText(data),mPaint_brokenline); //绘制底部空白处:数据的日期 String date=mdata.get(i).getDate(); canvas.drawText(date, gridspace_width * i + 10, heigh - brokenline_bottom / 2, mPaint_text); } }
绘制背景(二、循序渐进)
绘制折线+阴影
Ps:折线的绘制是根据圆点的位置来绘制的,阴影的绘制则是根据折线的路径进行绘制的,最后将路径闭合即可形成阴影了。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制折线图的白色背景 canvas.drawColor(Color.WHITE); //绘制灰色矩形区域 canvas.drawRect(10,0,width,heigh-brokenline_bottom,mPaint_bg); //绘制网格线,横向的;Y轴不变 X轴绘制直线 for (int j=0;j<4;j++){ canvas.drawLine(10,gridspace_heigh*(j+1),width,gridspace_heigh*(j+1),mPaint_gridline); } for (int i= 0; i < mdata.size();i++){ if (i==0){ //开始时需要将path移动到要开始绘制的位置,否则默认从(0,0)开始,绘制path路径 mpath.moveTo(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100); } //绘制纵向网格线 canvas.drawLine(gridspace_width * i + 10, 0, gridspace_width * i + 10, heigh - brokenline_bottom, mPaint_gridline); if (i!=mdata.size()-1){ //根据圆点位置绘制折线 canvas.drawLine(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, gridspace_width * (i + 1) + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i + 1).getData() / 100, mPaint_brokenline); //path的路径跟绘制的线的路径是相同的,因此path的起止点与线的起止点相同 mpath.quadTo(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, gridspace_width * (i + 1) + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i + 1).getData() / 100); } //绘制圆点,圆点位置根据网格线的位置确定 canvas.drawCircle(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, 10, mPaint_point_bg); canvas.drawCircle(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, 5, mPaint_point_sur); //绘制数据的数量 String data=mdata.get(i).getData()+""; canvas.drawText(data,gridspace_width*i+10,heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData()/100-mPaint_brokenline.measureText(data),mPaint_brokenline); //绘制底部空白处:数据的日期 String date=mdata.get(i).getDate(); canvas.drawText(date, gridspace_width * i + 10, heigh - brokenline_bottom / 2, mPaint_text); if (i==mdata.size()-1){ //绘制完最后一个点,path需要沿着纵轴向下到达heigh - brokenline_bottom 的位置再水平到达(10,heigh - brokenline_bottom )的位置,最后闭合 mpath.quadTo(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100,gridspace_width * i + 10, heigh - brokenline_bottom ); mpath.quadTo(gridspace_width * i + 10, heigh - brokenline_bottom ,10, heigh - brokenline_bottom); mpath.close(); } } //最终在画布上绘制path canvas.drawPath(mpath,mPaint_path); }
四、折线图完整代码
package widget.chart;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.util.AttributeSet;import android.util.Log;import android.view.View;import com.example.danfeng.myhongquanyingxiao.Base.AppApplication;import com.example.danfeng.myhongquanyingxiao.Base.SizeConvert;import java.util.List;import dao.BrokenLineCusVisit;/** * Created by Administrator on 2015/10/13. */public class BrokenLineCusVisitView extends View { private int width; private int heigh; //网格的宽度与高度 private int gridspace_width; private int gridspace_heigh; //底部空白的高度 private int brokenline_bottom; //灰色背景的画笔 private Paint mPaint_bg; //灰色网格的画笔 private Paint mPaint_gridline; //文本数据的画笔 private Paint mPaint_text; //折线圆点的蓝色背景 private Paint mPaint_point_bg; //折线圆点的白色表面 private Paint mPaint_point_sur; //阴影路径的画笔 private Paint mPaint_path; //折线的画笔 private Paint mPaint_brokenline; //路径 private Path mpath=new Path(); //客户拜访的折线(BrokenLineCusVisit)数据 private List mdata; public BrokenLineCusVisitView(Context context) { super(context); } public BrokenLineCusVisitView(Context context, AttributeSet attrs) { super(context, attrs); inite(context); } private void inite(Context context) { mPaint_bg=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_bg.setColor(Color.argb(0xff,0xef,0xef,0xef)); mPaint_gridline=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_gridline.setColor(Color.argb(0xff,0xce,0xCB,0xce)); mPaint_brokenline=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_brokenline.setColor(Color.argb(0xff,0x91,0xC8,0xD6)); mPaint_brokenline.setTextSize(18); mPaint_brokenline.setTextAlign(Paint.Align.CENTER); mPaint_point_bg=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_point_bg.setColor(Color.argb(0xff, 0x91, 0xC8, 0xD6)); mPaint_path=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_path.setColor(Color.argb(0x33,0x91,0xC8,0xD6)); mPaint_point_sur=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_point_sur.setColor(Color.WHITE); mPaint_text=new Paint(Paint.ANTI_ALIAS_FLAG); mPaint_text.setColor(Color.BLACK); mPaint_text.setTextAlign(Paint.Align.CENTER); invalidate(); } public List getMdata() { return mdata; } public void setMdata(List mdata) { this.mdata = mdata; requestLayout();//相当于调用onMeasure方法 invalidate();//相当于调用onDraw方法 } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.WHITE); canvas.drawRect(10,0,width,heigh-brokenline_bottom,mPaint_bg); //Y轴不变 X轴绘制直线 for (int j=0;j<4;j++){ canvas.drawLine(10,gridspace_heigh*(j+1),width,gridspace_heigh*(j+1),mPaint_gridline); } for (int i= 0; i < mdata.size();i++){ if (i==0){ mpath.moveTo(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100); } canvas.drawLine(gridspace_width * i + 10, 0, gridspace_width * i + 10, heigh - brokenline_bottom, mPaint_gridline); if (i!=mdata.size()-1){ canvas.drawLine(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, gridspace_width * (i + 1) + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i + 1).getData() / 100, mPaint_brokenline); mpath.quadTo(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, gridspace_width * (i + 1) + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i + 1).getData() / 100); } canvas.drawCircle(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, 10, mPaint_point_bg); canvas.drawCircle(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100, 5, mPaint_point_sur); String data=mdata.get(i).getData()+""; canvas.drawText(data,gridspace_width*i+10,heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData()/100-mPaint_brokenline.measureText(data),mPaint_brokenline); String date=mdata.get(i).getDate(); canvas.drawText(date, gridspace_width * i + 10, heigh - brokenline_bottom / 2, mPaint_text); if (i==mdata.size()-1){ mpath.quadTo(gridspace_width * i + 10, heigh - brokenline_bottom - (heigh - brokenline_bottom) * mdata.get(i).getData() / 100,gridspace_width * i + 10, heigh - brokenline_bottom ); mpath.quadTo(gridspace_width * i + 10, heigh - brokenline_bottom ,10, heigh - brokenline_bottom); mpath.close(); } } canvas.drawPath(mpath,mPaint_path); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); gridspace_width= 50; if(mdata.size()==0){ //通过调用onMeasure源码中的方法进行获得宽度, //宽度的获得有三种模式,具体介绍见博客底部连接 width=getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec); } else{ //根据数据条数设置宽度 width=gridspace_width*mdata.size()+10; } heigh=getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec); //设置底部白色空白的宽度 brokenline_bottom=50; gridspace_heigh=(heigh-brokenline_bottom)/4; setMeasuredDimension(width, heigh); }}
onMeasure方法的具体介绍
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 一句话锁定MySQL数据占用元凶
- Android获取数据时 浮点型整数位数值(超8位)过大导致科学计数法
- 超酷的计步器APP(一)——炫酷功能实现,自定义水波纹特效、自定义炫
- 在Android上用Canvas绘制音频波形图
- Android(安卓)不通过USB数据线调试的方法
- Android应用优化之流畅度
- android中如何在活动在回收时保存临时数据
- Android性能优化之系统显示原理