1.也是突然的想法,写了个柱状图,然后觉得静态的太死板又加上去了动画和点击事件.
2.下面开始上菜!各位道友觉得不好呢,就多给点批评建议,然后都给点个赞,哈哈~(原创作品,转载请标明)
(上传图片竟然不能超过2M,,,,,,,,,,,,本来做了个gif,一看9M多.....重新做了一个,,,就录了上方的一半将就看吧)
3.思路:一般自定义控件都要继承View或者一个控件,然后重写它的ondraw方法,在这个方法里面使用canvas各种浪.
4.柱状图控件代码,直接上了(细节思路,中途遇到的问题,存在的缺陷,心得等都在下面哦):
package com.jeska.testrecyclerview.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.Toast; import java.util.ArrayList; import java.util.HashMap; import java.util.Random; /** * Created by jeska on 2016/8/12. */ public class ZhuZhuangTu extends View { private Thread mThread; /**标题*/ private String title = "柱状图"; /***/ private Paint mPaint; private String emptyWarning = "没有数据"; private Context mContext; private ArrayList<HashMap<String,String>> mDataList; private ArrayList<HashMap<String,Float>> mLocationList; private int[] colors; /**整个空间的宽度*/ private int width; /**整个空间的高度*/ private int height; /**箭头宽度*/ private int arrowWidth = 10; /**坐标原点距离边界*/ private int distance = 30; /**底部辅助横线条数*/ private int horizontalLineCount = 10; /**柱形图的宽度*/ private float ZhuZhuangTuWidth; /**水平线间隔*/ private float horizontalLineInterval; /**柱状图间隔*/ private float ZhuZhuangTuInterval; private Random mRandom; /**用来做动画的可变值*/ // private float delta; /**用来做动画的线程while的flag*/ private boolean run = true; /**动画时长*/ private int millions = 6000; /**回调*/ private OnZhuZhuangTuClickListener mListener; /**设备密度*/ private float density; /**存放所有的delta*/ private float[] deltas; /**最大值出现的位置*/ private int maxValuePosition; public ZhuZhuangTu(Context context) { super(context); init(context); } public ZhuZhuangTu(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public ZhuZhuangTu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { this.mContext = context; mPaint = new Paint(); mLocationList = new ArrayList<>(); run = true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); setWillNotDraw(false); if (isDataMapEmpty()){ canvas.drawColor(Color.GRAY); mPaint.setColor(Color.MAGENTA); mPaint.setTextSize(40f); canvas.drawText(emptyWarning, width / 2 - mPaint.getTextSize()*emptyWarning.length()/2, height / 2-mPaint.getTextSize()/2, mPaint); }else{ mPaint.setColor(Color.BLACK); mPaint.setStrokeWidth(4); //绘制x轴 canvas.drawLine(distance * density, height - distance * density, width - distance * density, height - distance * density, mPaint); //绘制x轴箭头 canvas.drawLine(width - distance * density-arrowWidth * density, height - distance * density-arrowWidth * density, width - distance * density, height - distance * density, mPaint); canvas.drawLine(width - distance * density - arrowWidth * density, height - distance * density + arrowWidth * density, width - distance * density, height - distance * density, mPaint); //绘制y轴 canvas.drawLine(distance * density, distance * density, distance * density, height - distance * density, mPaint); //绘制y轴箭头 canvas.drawLine(distance * density - arrowWidth * density, distance * density + arrowWidth * density, distance * density, distance * density, mPaint); canvas.drawLine(distance * density + arrowWidth * density, distance * density + arrowWidth * density, distance * density, distance * density, mPaint); //绘制底部阶梯标准线 horizontalLineInterval = (height - 2 * distance * density) / horizontalLineCount; mPaint.setColor(Color.GRAY); mPaint.setStrokeWidth(1); float[] arr = getMaxValue(mDataList); if (arr[1] == 0){ return; } float average = arr[0] / (horizontalLineCount); mPaint.setTextSize(18); for (int i = 1 ;i<=horizontalLineCount;i++){ canvas.drawLine(distance * density, height - distance * density - horizontalLineInterval * i, width - distance * density, height - distance * density - horizontalLineInterval * i,mPaint); canvas.drawText(average * i+"",5f,height - distance * density - horizontalLineInterval * i + mPaint.getTextSize()/2,mPaint); } canvas.drawText("0", 5f, height - distance * density + mPaint.getTextSize() / 2, mPaint); mPaint.setStrokeWidth(3f); ZhuZhuangTuInterval = (width - 2 * distance * density)/ mDataList.size(); ZhuZhuangTuWidth = ZhuZhuangTuInterval/3; mLocationList.clear(); for (int i = 0;i< mDataList.size();i++){ mPaint.setColor(colors[i]); float value = Float.parseFloat(mDataList.get(i).get("value").toString()); //从上到下的动画 // canvas.drawRect(distance * density + ZhuZhuangTuInterval * i + ZhuZhuangTuInterval / 2 - ZhuZhuangTuWidth / 2, // height - distance * density - (height - 2 * distance * density) * (value / arr[0]), // distance * density + ZhuZhuangTuInterval * i + ZhuZhuangTuInterval / 2 + ZhuZhuangTuWidth / 2 // , height - distance * density - delta, mPaint); //从下到上的动画,所有的图形的绘制时间都是一样的(delta一样),也就是说所有柱子同时绘制完成,长的绘制早,短的绘制晚. canvas.drawRect(distance * density + ZhuZhuangTuInterval * i + ZhuZhuangTuInterval / 2 - ZhuZhuangTuWidth / 2, height - distance * density - (height - 2 * distance * density) * (value / arr[0]) + deltas[i], distance * density + ZhuZhuangTuInterval * i + ZhuZhuangTuInterval / 2 + ZhuZhuangTuWidth / 2 , height - distance * density, mPaint); //若要所有的柱子同时开始绘制,需要给每个柱子添加一个delta.并且要对动画时间分别控制. // System.out.println(".........."+deltas[i]); HashMap<String,Float> map = new HashMap<>(); map.put("left",distance * density + ZhuZhuangTuInterval * i + ZhuZhuangTuInterval / 2 - ZhuZhuangTuWidth / 2); map.put("top",height - distance * density - (height - 2 * distance * density) * (value / arr[0])); map.put("right",distance * density + ZhuZhuangTuInterval * i + ZhuZhuangTuInterval / 2 + ZhuZhuangTuWidth / 2); map.put("bottom",height - distance * density + deltas[i]); mLocationList.add(map); mPaint.setColor(Color.BLACK); mPaint.setTextSize(20); String name = mDataList.get(i).get("name").toString(); canvas.drawText(name,distance* density+ZhuZhuangTuInterval*i+ZhuZhuangTuInterval/2-(name.length()/2)*mPaint.getTextSize(), height-distance* density+mPaint.getTextSize(),mPaint); String unit = mDataList.get(i).get("unit").toString(); String describe = value+unit; canvas.drawText(describe, distance* density+ZhuZhuangTuInterval*i+ZhuZhuangTuInterval/2-ZhuZhuangTuWidth/2, height - distance * density - (height - 2 * distance * density) * (value / arr[0]),mPaint); } mPaint.setColor(Color.BLACK); canvas.drawText("unit:"+mDataList.get(0).get("unit").toString(),0,distance* density/2,mPaint); mPaint.setTextSize(30); canvas.drawText(title,width/2-title.length()/2*mPaint.getTextSize(),distance* density,mPaint); } if (stop(deltas) && !mThread.isAlive()){ if (null != mListener){ mListener.onDrawFinished(); } } } /**是否为空,同来判断绘制是否完成*/ private boolean stop(float[] arr){ for(float f : arr){ if (f != 0){ return false; } } return true; } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: float x = event.getX(); float y = event.getY(); for (int i = 0;i<mLocationList.size();i++){ if (x>=mLocationList.get(i).get("left") && x<=mLocationList.get(i).get("right") && y >=mLocationList.get(i).get("top") && y<=mLocationList.get(i).get("bottom")){ if (null != mListener){ mListener.onZhuZhuangTuClick(i,mLocationList.get(i)); } } } break; } return super.onTouchEvent(event); } /**获取集合中的最大值*/ private float[] getMaxValue(ArrayList<HashMap<String,String>> list) { try { float max = Float.parseFloat(list.get(0).get("value").toString()); for (int i = 0;i<list.size();i++){ float temp = Float.parseFloat(list.get(i).get("value").toString()); if (temp > max){ max = temp; } } return new float[]{max,1}; }catch (Exception e){ return new float[]{-1,0}; } } /**获取最大值出现的位置*/ private int getMaxValuePosition(ArrayList<HashMap<String,String>> list){ try { int j = 0; float max = Float.parseFloat(list.get(0).get("value").toString()); for (int i = 0;i<list.size();i++){ float temp = Float.parseFloat(list.get(i).get("value").toString()); if (temp > max){ j = i; } } return j; }catch (Exception e){ return 0; } } /**设置数据*/ public void setDataMap(ArrayList<HashMap<String,String>> list){ if (null == list || list.size() ==0){ return; } this.mDataList = list; colors = new int[mDataList.size()]; mRandom = new Random(); for (int i = 0;i<mDataList.size();i++){ int r = mRandom.nextInt(256); int g= mRandom.nextInt(256); int b = mRandom.nextInt(256); int color = Color.rgb(r, g, b); colors[i] = color; } } /**设置动画时间*/ public void setAnimationDurtion(int millions){ this.millions = millions; } /**监察数据集合是否为空*/ private boolean isDataMapEmpty(){ if (null == mDataList || mDataList.size() ==0){ return true; }else{ return false; } } /**设置回调*/ public void setOnZhuZhuangTuClickListener(OnZhuZhuangTuClickListener mListener){ this.mListener = mListener; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = this.getMeasuredWidth(); height = this.getMeasuredHeight(); density = getDensity(); maxValuePosition = getMaxValuePosition(mDataList); // delta = height - 2*distance*density; //获取所有的delta if (!isDataMapEmpty()){ deltas = new float[mDataList.size()]; float m = getMaxValue(mDataList)[0]; for (int i =0;i<mDataList.size();i++){ float v = Float.parseFloat(mDataList.get(i).get("value").toString()); float delta = (height - 2*distance*getDensity())*(v/m); deltas[i] = delta; } } } @Override public void draw(Canvas canvas) { super.draw(canvas); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override public void layout(int l, int t, int r, int b) { super.layout(l, t, r, b); mThread = new Thread(new Runnable() { @Override public void run() { while (run){ try { Thread.sleep(15); for (int i =0;i<deltas.length;i++){ // delta = delta - (height - 2*distance*density)/(millions/20);//做向下的动画时使用 float delta = deltas[i]; float m = getMaxValue(mDataList)[0]; float v = Float.parseFloat(mDataList.get(i).get("value").toString()); float time = millions*(v/m); if (time <= 800){ time = 800; } delta = delta - (height - 2*distance*density)*(v /m)/(time/15); if (delta <=0){ delta = 0; if (i==maxValuePosition){ run = false; } } deltas[i] = delta; } postInvalidate(); } catch (InterruptedException e) { e.printStackTrace(); } } } }); mThread.start(); } /**获取设备密度*/ private float getDensity(){ DisplayMetrics metric = new DisplayMetrics(); WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); manager.getDefaultDisplay().getMetrics(metric); return metric.density; } /**为柱状图设置标题*/ public void setTitle(String title) { this.title = title; } }
5.note:
1>接收数据的集合是我写死的ArrayList<HashMap<String,String>>,hashmap的键必须有name(每个柱子的名称),value(每个柱子的值),unit(单位);这些都是可以改的,根据自己的需要改就行了.
2>获取控件的宽高,并且将高度分成10份,从0到集合中数据的最大值,分为10个刻度绘制在y轴的左侧,并给每个刻度加上水平线.(这一块我没优化,根据数据来的,有时候可能刻度都不是整数,根据需要自己调整吧.)
3>x轴是按照数据集合中柱子的数量n,平分为n段,每一段再分成3份,取中间的一份画柱子,左右的两份空白.
4>在ondraw中,刻度,柱子名称,柱子高度,图表名称,水平线等是一次绘制,而柱状图是循环绘制.
5>ArrayList<HashMap<String,Float>>mLocationList用来存放所有的柱子的"左上右下"值,用以点击事件中.
6>每个柱子的颜色都是随机的
7>deltas数组存放着所有柱子的变化高度,在onlayout中开启的线程,控制着数组的值,每次数值变化后使用postInvalidate()重新绘制,所以每个柱子都是同时开始绘制的,并且完成时间是按照自身的高度.(这个可能要具体体会一下了,,,,,,)
8>还有些细节的东西慢慢看吧......
6.回调代码:
package com.jeska.testrecyclerview.view; import java.util.HashMap; /** * Created by jeska on 2016/8/12. */ public interface OnZhuZhuangTuClickListener { void onZhuZhuangTuClick(int position,HashMap<String,Float> locationMap); void onDrawFinished(); }7.使用,先上布局文件:等分成了4块
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:orientation="horizontal" > <com.jeska.testrecyclerview.view.ZhuZhuangTu android:id="@+id/zzt1" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"/> <com.jeska.testrecyclerview.view.ZhuZhuangTu android:id="@+id/zzt2" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" > <com.jeska.testrecyclerview.view.ZhuZhuangTu android:id="@+id/zzt3" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"/> <com.jeska.testrecyclerview.view.ZhuZhuangTu android:id="@+id/zzt4" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent"/> </LinearLayout> </LinearLayout>8.activity代码:
package com.jeska.testrecyclerview; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Toast; import com.jeska.testrecyclerview.view.OnZhuZhuangTuClickListener; import com.jeska.testrecyclerview.view.ZhuZhuangTu; import java.util.ArrayList; import java.util.HashMap; /** * Created by jeska on 2016/8/12. */ public class ZhuZhuangTuActivity extends AppCompatActivity { private ZhuZhuangTu zzt1; private ZhuZhuangTu zzt2; private ZhuZhuangTu zzt3; private ZhuZhuangTu zzt4; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_zhuzhuangtu); zzt1 = (ZhuZhuangTu) findViewById(R.id.zzt1); zzt2 = (ZhuZhuangTu) findViewById(R.id.zzt2); zzt3 = (ZhuZhuangTu) findViewById(R.id.zzt3); zzt4 = (ZhuZhuangTu) findViewById(R.id.zzt4); final ArrayList<HashMap<String,String>> list = new ArrayList<>(); HashMap<String,String> map1 = new HashMap<>(); map1.put("name","初中"); map1.put("value", "100"); map1.put("unit", "元"); list.add(map1); HashMap<String,String> map2 = new HashMap<>(); map2.put("name","高中"); map2.put("value", "300"); map2.put("unit", "元"); list.add(map2); HashMap<String,String> map3 = new HashMap<>(); map3.put("name","大学"); map3.put("value", "500"); map3.put("unit", "元"); list.add(map3); HashMap<String,String> map4 = new HashMap<>(); map4.put("name","研究生"); map4.put("value", "1000"); map4.put("unit", "元"); list.add(map4); HashMap<String,String> map5 = new HashMap<>(); map5.put("name","博士"); map5.put("value", "2000"); map5.put("unit", "元"); list.add(map5); HashMap<String,String> map6 = new HashMap<>(); map6.put("name","硕导"); map6.put("value", "3000"); map6.put("unit", "元"); list.add(map6); HashMap<String,String> map7 = new HashMap<>(); map7.put("name","博导"); map7.put("value", "4000"); map7.put("unit", "元"); list.add(map7); HashMap<String,String> map8 = new HashMap<>(); map8.put("name","boss"); map8.put("value", "6000"); map8.put("unit", "元"); list.add(map8); zzt1.setDataMap(list); zzt1.setAnimationDurtion(3000); zzt1.setTitle("学历与日薪"); zzt1.setOnZhuZhuangTuClickListener(new OnZhuZhuangTuClickListener() { @Override public void onZhuZhuangTuClick(int position, HashMap<String, Float> locationMap) { Toast.makeText(ZhuZhuangTuActivity.this, "第" + position + "个:" + list.get(position).get("name") + ":" + list.get(position).get("value") + list.get(position).get("unit"), Toast.LENGTH_SHORT).show(); } @Override public void onDrawFinished() { Toast.makeText(ZhuZhuangTuActivity.this,"zzt1...绘制完成", Toast.LENGTH_SHORT).show(); } }); zzt4.setDataMap(list); zzt4.setAnimationDurtion(18000); zzt4.setTitle("学历与日薪"); zzt4.setOnZhuZhuangTuClickListener(new OnZhuZhuangTuClickListener() { @Override public void onZhuZhuangTuClick(int position, HashMap<String, Float> locationMap) { Toast.makeText(ZhuZhuangTuActivity.this, "第" + position + "个:" + list.get(position).get("name") + ":" + list.get(position).get("value") + list.get(position).get("unit"), Toast.LENGTH_SHORT).show(); } @Override public void onDrawFinished() { Toast.makeText(ZhuZhuangTuActivity.this,"zzt4...绘制完成", Toast.LENGTH_SHORT).show(); } }); final ArrayList<HashMap<String,String>> list2 = new ArrayList<>(); HashMap<String,String> map11 = new HashMap<>(); map11.put("name", "动漫"); map11.put("value", "1000"); map11.put("unit", "部"); list2.add(map11); HashMap<String,String> map12 = new HashMap<>(); map12.put("name", "电视剧"); map12.put("value", "200"); map12.put("unit", "部"); list2.add(map12); HashMap<String,String> map13 = new HashMap<>(); map13.put("name", "电影"); map13.put("value", "500"); map13.put("unit", "部"); list2.add(map13); HashMap<String,String> map14 = new HashMap<>(); map14.put("name", "综艺"); map14.put("value", "1500"); map14.put("unit", "部"); list2.add(map14); zzt2.setDataMap(list2); zzt2.setAnimationDurtion(8000); zzt2.setTitle("视频的崛起"); zzt2.setOnZhuZhuangTuClickListener(new OnZhuZhuangTuClickListener() { @Override public void onZhuZhuangTuClick(int position, HashMap<String, Float> locationMap) { Toast.makeText(ZhuZhuangTuActivity.this, "第" + position + "个:" + list2.get(position).get("name") + ":" + list2.get(position).get("value") + list2.get(position).get("unit"), Toast.LENGTH_SHORT).show(); } @Override public void onDrawFinished() { Toast.makeText(ZhuZhuangTuActivity.this, "zzt2...绘制完成", Toast.LENGTH_SHORT).show(); } }); zzt3.setDataMap(list2); zzt3.setAnimationDurtion(13000); zzt3.setTitle("视频的崛起"); zzt3.setOnZhuZhuangTuClickListener(new OnZhuZhuangTuClickListener() { @Override public void onZhuZhuangTuClick(int position, HashMap<String, Float> locationMap) { Toast.makeText(ZhuZhuangTuActivity.this, "第" + position + "个:" + list2.get(position).get("name") + ":" + list2.get(position).get("value") + list2.get(position).get("unit"), Toast.LENGTH_SHORT).show(); Snackbar snackbar = Snackbar.make(zzt3,"VideoView方式查看视频",Snackbar.LENGTH_INDEFINITE); snackbar.setAction("确定", new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(ZhuZhuangTuActivity.this, VideoActivity.class); startActivity(intent); } }); snackbar.setActionTextColor(Color.MAGENTA); snackbar.show(); } @Override public void onDrawFinished() { Toast.makeText(ZhuZhuangTuActivity.this, "zzt3...绘制完成", Toast.LENGTH_SHORT).show(); } }); } }9.好了,全都奉献了,写的急,可能有不妥的地方,还望诸位道友批评指正啦,