智能电视TV开发---曲线图绘制

来源:互联网 发布:一个月学会java 编辑:程序博客网 时间:2024/04/27 17:00

 绘制曲线图有几个基本的元素:坐标轴,点,线,分清楚变化的和不变得,从而进行绘制,其实在智能电视TV开发---拍照+水印   中在拍照的图片上面绘制水印都是一样的原理,就是以一张图片为基准,取得srcBitmap的canvas,然后再使用canvas来把其他的内容绘制上去。网上也有几个开源的绘制图表的项目,但是具体到自己项目里面都不太适用。接下来我会用一个二手房房价行情的数据来绘制6个月内容的趋势图,水平绘制,宽比高长,左右可以拖动,适用于嵌入页面的,而不是占据整个屏幕。分部截图来各个部分的实现关键内容,最后贴出代码。

      一、背景以及坐标轴的制作

      先看一下效果图:

从上图中水平轴是月份,竖直轴包含两部分分别是销售金额以及销售的户数,对比这两幅图片,你可以看到左边部分是不动的,而底部的月份是可以滑动的,使用的是scroller来实现的。上面的内容基本上都是使用canvas来绘制上去的,所以要计算好位置。

/** * 主要绘制可以滚动的,但是内容不变的 * 刻度 * 月份,年 * 水平的虚线 * 竖直的阴影矩形 * 当数据不为空的时候,绘制折线 */public void drawScrollStaticView(){//清除屏幕viewBgCanvas.drawColor(0, PorterDuff.Mode.CLEAR);float timesTop = scroll_bottom;float scrollTimesTop = scroll_bottom;float moneyTop = scroll_bottom;float scrollMoneyTop = scroll_bottom;int drawMonth = currentMonth;int rightMarg = view_width - virtical_graduate_right;float yearWidth = GetTextWH.getTextW("0000年", yearTextPaint);//绘制水平月份刻度,总共countMonths个刻度for (int i = 0; i < countMonths; i++) {int temp = rightMarg - virtical_graduate_width;virtical_graduate.setBounds(temp, scroll_bottom, rightMarg, 10+scroll_bottom);virtical_graduate.draw(viewBgCanvas);//绘制月份数字        viewBgCanvas.drawText(monthArrays[drawMonth], temp-monthTextW / 2, scroll_bottom + monthTextH+16, monthPaint);//绘制月份数字        //绘制月份的月        viewBgCanvas.drawText("月", temp - monthTextW / 4, scroll_bottom + monthTextH * 2 + 16, monthTextPaint);//绘制月        //绘制年        if (drawMonth == 0 && i < countMonths) {        viewBgCanvas.drawText(limitYears[(i / 12)], temp-yearWidth / 2, scroll_bottom + monthTextH * 3+16, yearTextPaint);}      //绘制水平的虚线        int virtical_bottom = broken_divider_height;//虚线竖直高度        for (int j = 0; j < 3; j++) {        broken_divider.setBounds(temp, virtical_bottom, rightMarg, 2+virtical_bottom);        broken_divider.draw(viewBgCanvas);        virtical_bottom += broken_divider_height;}                //绘制竖直的阴影矩形        if ((i & 0x1) == 0)        viewBgCanvas.drawRect(temp, 6, rightMarg, scroll_bottom, rectShaderPaint);                //当数据不为空的时候绘制数据折线图        if (modelInfos != null) {if (i > actualMonths-1) {//当绘制的月数大约时间的月数的时候timesTop = scroll_bottom;moneyTop = scroll_bottom;scrollTimesTop = scroll_bottom;scrollMoneyTop = scroll_bottom;drawBrokenLine(viewBgCanvas,"", timesTop, scrollTimesTop, "",moneyTop, scrollMoneyTop, temp);}else if (i == actualMonths -1) {timesTop = modelInfos[i].getSaleHouseCountTop();moneyTop = modelInfos[i].getSaleMoneyTop();scrollTimesTop = scroll_bottom;scrollMoneyTop = scroll_bottom;drawBrokenLine(viewBgCanvas,modelInfos[i].getSaleHouseCountText(), timesTop, scrollTimesTop, "¥"+modelInfos[i].getSaleMoney(), moneyTop, scrollMoneyTop, temp);}else if (i < actualMonths-1) {timesTop = modelInfos[i].getSaleHouseCountTop();moneyTop = modelInfos[i].getSaleMoneyTop();scrollTimesTop = modelInfos[i+1].getSaleHouseCountTop();scrollMoneyTop = modelInfos[i+1].getSaleMoneyTop();drawBrokenLine(viewBgCanvas, modelInfos[i].getSaleHouseCountText(), timesTop, scrollTimesTop, "¥"+modelInfos[i].getSaleMoney(), moneyTop, scrollMoneyTop, temp);}}                int drawMonth_1 = drawMonth -1;        if (drawMonth_1 < 0) {        drawMonth_1 = 11;}        drawMonth = drawMonth_1;        rightMarg = temp;}viewLeftBgCanvas.drawColor(0, PorterDuff.Mode.CLEAR);drawScrollStaticLeftView(viewLeftBgCanvas);}
/** * 绘制左侧的销售金额和销售的房数 * @param canvas */private void drawScrollStaticLeftView(Canvas canvas){report_trend_abscissa_bg.setBounds(0, 0, virtical_graduate_width, scroll_height);report_trend_abscissa_bg.draw(canvas);float textH = GetTextWH.getTextH(leftTextPaint);float textMoneyLeft = 4F * density;int bottom_marge = broken_divider_height;for (int i = 0; i < 3; ++i){report_trend_abscissa_divider.setBounds(0, bottom_marge, virtical_graduate_width, bottom_marge + 2);report_trend_abscissa_divider.draw(canvas);canvas.drawText(limitMoneys[i], textMoneyLeft, bottom_marge + textH*3/2, leftTextPaint);canvas.drawText(limitSalesTimes[i], textMoneyLeft, bottom_marge + textH * 5/2 + 6, leftTextPaint);bottom_marge += broken_divider_height;    }}


上面两段代码就是实现上面图片的关键部分。里面也解释的很详细

二、计算间距

在绘制文字的时候我们需要用到x,y而要合理的放置文字,我们还需要知道绘制文字的长度和高度,下面类就是获取文字长度和高度代码:

package com.jwzhangjie.smarttv_client.ui.trendview;import android.graphics.Paint;public class GetTextWH {public static float getTextH(Paint paramPaint)  {/** * 为了说明,这里引用辅助线baseline=0,画文字的y坐标top:文字的最顶端离baseline的距离(ascent的极限距离负数)ascent:文字的上端离baseline的距离(负数)descent:文字的下端离baseline的距离(正数)bottom:文字最下端的距离(descent的极限距离正数)lead:上行文字descent到下行ascent的距离                                                                                                           */    Paint.FontMetrics localFontMetrics = paramPaint.getFontMetrics();    return (-(localFontMetrics.ascent + localFontMetrics.descent));  }  public static float getTextW(String paramString, Paint paramPaint)  {    return paramPaint.measureText(paramString);  }}
/**  * 计算高度,宽度  */ public void initMeage(){  layout_width = getWidth();//得到自己的宽度px  layout_height = getHeight();//得到自己的高度px  layout_width_34 = (int)(0.75F * layout_width);  virtical_graduate_width = dip2px(60);//一个虚拟刻度的高度  virtical_graduate_right = dip2px(20);//第一个虚拟刻度距离顶部的高度  int margeTop = Math.round(44.0F * density);//距离左边44dip  int margeBottom = Math.round(42.0F * density);//竖直的虚拟刻度线距离右边42dip  //6指的是竖直箭头也有宽度的,三角头右边一部分,整个箭头的一半是9px  virtical_graduate_bottom = layout_height - margeBottom - 6;  monthTextW = GetTextWH.getTextW("00", monthPaint);//计算出月份的数字的宽度  monthTextH = GetTextWH.getTextH(monthPaint);//绘制的月份中央对齐刻度线  float monthTextW = GetTextWH.getTextW("月", monthTextPaint);  drawMonthTop = layout_height - (int)((monthTextW + margeBottom + monthTextW) / 2.0F);  drawYearTop = layout_height - (int)((virtical_graduate_bottom + GetTextWH.getTextW("2013年", yearTextPaint)) / 2F);  scroll_top = margeTop + 8;  scroll_bottom = virtical_graduate_bottom - 6;  scroll_height = scroll_bottom;  broken_divider_height = scroll_height / 4;//分为4份  viewBottomBgBitmap_left = margeBottom;  calculateActualWidth();  initViewBg();  initViewBottomBg(); }

三、将背景绘制在自定义的View上面


首先我们要覆写onDraw方法,如下:

@Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(viewBgBitmap, -view_width+layout_width-virtical_graduate_right, 0, viewBgPaint);//绘制左边的部分,由于要固定在左边,所以x设置为getScrollX(),不然移动的canvas.drawBitmap(viewLeftBgBitmap, getScrollX(), 0, viewBgPaint);}

@Overrideprotected void onLayout(boolean changed, int left, int top, int right,int bottom) {super.onLayout(changed, left, top, right, bottom);if (!isNotFirst) {//只需要执行一次就可以了initMeage();drawScrollStaticView();isNotFirst = true;}}

四、触摸滑动

这部分首先要使用的就是onTouchEvent实现移动效果,计算移动的位置,松开手后回滚。

@Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);float x = event.getX();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mLastMotionX = x;//动画还没有结束就按下了就结束动画if (!mScroller.isFinished()) {mScroller.abortAnimation();}break;case MotionEvent.ACTION_MOVE:int deltaX = (int) (mLastMotionX - x);mLastMotionX = x;if (getScrollX() < layout_width_34 && getScrollX() > -(layout_width_34+scroll_width)) {scrollBy(deltaX, 0);}invalidate();break;case MotionEvent.ACTION_UP:if (getScrollX() > 0 || view_width <= layout_width) {mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);}else if (getScrollX() < -scroll_width) {mScroller.startScroll(getScrollX(), 0, -getScrollX()-scroll_width, 0, 500);}invalidate();break;}return true;}


其实在case MotionEvent.ACTION_UP:里面还应该处理一种情况就是介于另外两种情况之间的,手在View上面快速滑动并松开手,刻度应该自己慢慢滑动到一端,现在先不实现,如果自己需要也可以添加,里面需要的方法就已经写了,直接调用就行。

五、更新数据

更新数据需要封装一个类,包含价格,售房的户数,以及保存距离顶端的位置等,代码如下:

package com.jwzhangjie.smarttv_client.ui.trendview;import android.os.Parcel;import android.os.Parcelable;public class ModelInfo implements Parcelable{@Overridepublic int describeContents() {return 0;}public ModelInfo(){}private ModelInfo(Parcel source){readFromParcel(source);}private double saleMoney;//销售金额private int saleCount;//售房户数private String saleMoneyText;//销售金额显示在布局的内容private String saleHouseCountText;//销售楼房个数显示在布局的内容private float saleMoneyTop;//销售金额显示在布局的顶部距离private float saleHouseCountTop;//刷卡次数显示在布局顶部距离private double maxMoney;//最大的销售金额private int maxCount;//最大的售房户数public void readFromParcel(Parcel source){saleMoney = source.readDouble();saleCount = source.readInt();saleMoneyText = source.readString();saleHouseCountText = source.readString();saleMoneyTop = source.readFloat();saleHouseCountTop = source.readFloat();maxMoney = source.readDouble();maxCount = source.readInt();}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeDouble(saleMoney);dest.writeInt(saleCount);dest.writeString(saleMoneyText);dest.writeString(saleHouseCountText);dest.writeFloat(saleMoneyTop);dest.writeFloat(saleHouseCountTop);dest.writeDouble(maxMoney);dest.writeInt(maxCount);}public static Creator<ModelInfo> CREATOR = new Creator<ModelInfo>() {@Overridepublic ModelInfo createFromParcel(Parcel source) {return new ModelInfo(source);}@Overridepublic ModelInfo[] newArray(int size) {return new ModelInfo[size];}};public double getSaleMoney() {return saleMoney;}public void setSaleMoney(double saleMoney) {this.saleMoney = saleMoney;}public int getSaleCount() {return saleCount;}public void setSaleCount(int saleCount) {this.saleCount = saleCount;}public String getSaleMoneyText() {return saleMoneyText;}public void setSaleMoneyText(String saleMoneyText) {this.saleMoneyText = saleMoneyText;}public String getSaleHouseCountText() {return saleHouseCountText;}public void setSaleHouseCountText(String saleHouseCountText) {this.saleHouseCountText = saleHouseCountText;}public float getSaleMoneyTop() {return saleMoneyTop;}public void setSaleMoneyTop(float saleMoneyTop) {this.saleMoneyTop = saleMoneyTop;}public float getSaleHouseCountTop() {return saleHouseCountTop;}public void setSaleHouseCountTop(float saleHouseCountTop) {this.saleHouseCountTop = saleHouseCountTop;}public double getMaxMoney() {return maxMoney;}public void setMaxMoney(double maxMoney) {this.maxMoney = maxMoney;}public int getMaxCount() {return maxCount;}public void setMaxCount(int maxCount) {this.maxCount = maxCount;}}


实现更新的方法如下:

/** *更新数据  *将数据进行处理,计算每一个点距离布局的左边位置 *然后重新绘制刻度等图像,调用 drawScrollStaticView() *@param maxMoney  最大的售房金额 *@param maxTimes  最大的售房户数 */public void updateData(ArrayList<ModelInfo> lists, double maxMoney, int maxTimes){actualMonths = lists.size();modelInfos = new ModelInfo[actualMonths];//RangMaxMoney  返回刻度的最高金额int RangMaxMoney = DataAccuracy.FomatMoneyString(limitMoneys, maxMoney);//RangMaxTimes 返回刻度的最大户数int RangMaxTimes = DataAccuracy.FomatTimesString(limitSalesTimes, maxTimes);for (int i = 0; i < actualMonths; i++) {ModelInfo modelInfo = lists.get(i);float test = scroll_height - (float)(modelInfo.getSaleMoney() / RangMaxMoney * scroll_height);float testTimes = scroll_height - (float)((double)modelInfo.getSaleCount() / RangMaxTimes * scroll_height);modelInfo.setSaleMoneyTop(test);modelInfo.setSaleHouseCountTop(testTimes);modelInfos[i] = modelInfo;}drawScrollStaticView();invalidate();}

六、最终的实现效果和源码

6.1、主类,负责模拟数据,更新数据

package com.jwzhangjie.smarttv_client.ui.trendview;import java.util.ArrayList;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import com.jwzhangjie.smarttv_client.R;import com.jwzhangjie.smarttv_client.ui.base.BaseActivity;public class Trend extends BaseActivity{private MyTrendView trendView;private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_trend_view);trendView = (MyTrendView)findViewById(R.id.trendView);button = (Button)findViewById(android.R.id.button1);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {ArrayList<ModelInfo> modelInfos = new ArrayList<ModelInfo>();ModelInfo modelInfo = new ModelInfo();modelInfo.setMaxCount(13);modelInfo.setMaxMoney(11200);modelInfo.setSaleMoney(11200);modelInfo.setSaleCount(13);modelInfo.setSaleHouseCountText("11200元");modelInfo.setSaleHouseCountText("13户");modelInfos.add(modelInfo);ModelInfo modelInfo1 = new ModelInfo();modelInfo1.setSaleMoney(9000);modelInfo1.setSaleCount(8);modelInfo1.setSaleHouseCountText("9000元");modelInfo1.setSaleHouseCountText("8户");modelInfos.add(modelInfo1);ModelInfo modelInfo2 = new ModelInfo();modelInfo2.setSaleMoney(9000);modelInfo2.setSaleCount(3);modelInfo2.setSaleHouseCountText("9000元");modelInfo2.setSaleHouseCountText("3户");modelInfos.add(modelInfo2);ModelInfo modelInfo3 = new ModelInfo();modelInfo3.setSaleMoney(9200);modelInfo3.setSaleCount(6);modelInfo3.setSaleHouseCountText("9200元");modelInfo3.setSaleHouseCountText("6户");modelInfos.add(modelInfo3);trendView.updateData(modelInfos, 11200, 13);}});}}


6.2、主类的布局

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <Button      android:layout_width="wrap_content"     android:layout_height="wrap_content"     android:text="更新数据"     android:id="@android:id/button1"        /><include     layout="@layout/view_trend"    android:layout_width="300dip"    android:layout_height="200dip"    android:layout_centerInParent="true"    /></RelativeLayout>

6.3、趋势图的布局

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@drawable/trend_bg_repeat" >    <ImageView        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="-2dip"        android:background="@drawable/report_trend_notch"        android:contentDescription="@string/app_name" />    <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_marginBottom="42.0dip"        android:gravity="bottom" >        <ImageView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:background="@drawable/report_trend_timeline"            android:contentDescription="@string/app_name" />    </LinearLayout><com.jwzhangjie.smarttv_client.ui.trendview.MyTrendView     android:id="@+id/trendView"    android:layout_width="match_parent"    android:layout_height="match_parent"/></FrameLayout>


6.4、辅助类DataAccuracy.java

package com.jwzhangjie.smarttv_client.ui.trendview;public class DataAccuracy {private static int getMaxMoney(double paramDouble)  {    if (paramDouble < 4000.0D)      return 4000;    return (8000 * (int)Math.pow(2.0D, (int)b(paramDouble / 4000.0D)));  }  private static int getMaxTimes(int paramInt)  {    if (paramInt < 8)      return 8;    return (16 * (int)Math.pow(2.0D, (int)b(paramInt / 8)));  }  public static int FomatMoneyString(String[] paramArrayOfString, double paramDouble)  {    int i = getMaxMoney(paramDouble);    for (int j = 0; j < 3; ++j)      paramArrayOfString[j] = FormatDecimal.format(i * (3 - j) / 4);    return i;  }  public static int FomatTimesString(String[] paramArrayOfString, int paramInt)  {    int i = getMaxTimes(paramInt);    for (int j = 0; j < 3; ++j)      paramArrayOfString[j] = (i * (3 - j) / 4) + "次";    return i;  }  private static double b(double paramDouble)  {    return (Math.log(paramDouble) / Math.log(2.0D));  }}

6.5、辅助类FormatDecimal.java

package com.jwzhangjie.smarttv_client.ui.trendview;import java.text.DecimalFormat;public class FormatDecimal {private static DecimalFormat a = new DecimalFormat("#,###");private static DecimalFormat b = new DecimalFormat("#.00");private static DecimalFormat c = new DecimalFormat("#,##0.00");private static DecimalFormat d = new DecimalFormat("0.0");private static DecimalFormat e = new DecimalFormat("#0");  public static String format(double paramDouble)  {    return a.format(paramDouble);  }    public static String formatTwo(double params){  return b.format(params);  }    public static String formatThree(double params){  return c.format(params);  }  public static String formatFour(double params){  return d.format(params);  }  public static String formatFive(double params){  return e.format(params);  }}

6.6、自定义的趋势图类

package com.jwzhangjie.smarttv_client.ui.trendview;import java.util.ArrayList;import java.util.Date;import com.jwzhangjie.smarttv_client.R;import android.content.Context;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.drawable.Drawable;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.widget.Scroller;public class MyTrendView extends View {private float density;private float mLastMotionX;//记录上一次触摸的x的坐标private boolean isNotFirst = false;//判断是否是第一次,如果是第一次则调用initMeage,绘制位图,这些是不变// 图片资源private Drawable virtical_graduate;// 刻度线private Drawable broken_divider;//水平的虚线private Drawable report_trend_abscissa_bg;//左边的渐进图片private Drawable report_trend_abscissa_divider;//左边渐进部分的水平分割线private int countMonths = 12;//总共需要绘制的月数这里默认设置12个月private int actualMonths = 0;//传递的数据,总共的月数private int currentMonth;//当前的月份private int currentYear;//当前的年份private int layout_width;//当前布局的宽度private int layout_height;//当前布局的高度private int layout_width_34;//当前布局的3/4private int view_width;//实际View的宽度,要实现滚动private int virtical_graduate_width;//一个虚拟刻度的宽度,这里设置60dipprivate int virtical_graduate_right;//虚拟刻度距离右边顶部20dipprivate int virtical_graduate_bottom;private int scroll_width;//可以滚动的距离,实际宽度-布局本身宽度private int scroll_height;private int scroll_top;//可以滚动的阴影距离左边的距离private int scroll_bottom;//可以滚动阴影距离布局右边的距离private int broken_divider_height;//水平虚线之间的间距private int viewBottomBgBitmap_left;//底部距离布局顶部的高度private String[] monthArrays = { "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12" };private String[] limitYears = new String[3];//今年,去年,前年private String[] limitMoneys = new String[3];//存放售房的最高额,底部3000,2000,1000private String[] limitSalesTimes = new String[3];//存放售房的个数,24,16,8private float monthTextW;//文字的宽度private float monthTextH;//计算出文字的高度/2,为了与刻度线对齐private int drawMonthTop;//绘制月数字位于父布局上边的距离private int drawYearTop;//绘制右边年的left位置private int minimumFlingVelocitx; //滚动的最小速度private int maxmumFlingVelocitx; //滚动的最大速度private ModelInfo[] modelInfos = null;/** * 两个位图 * @viewBgBitmap 是用来加载刻度,月份,年,水平矩形,竖直虚线 * @viewBottomBgBitmap 来用绘制底部 */private Bitmap viewBgBitmap;private Bitmap viewLeftBgBitmap;/** * Paint声明 */private Paint viewBgPaint;private Paint rectShaderPaint;//竖直的矩形阴影private Paint monthPaint;private Paint monthTextPaint;private Paint yearTextPaint;private Paint leftTextPaint;//View左部渐进里面文本的Paintprivate Paint moneyBrokenLine;//金额的折线Paintprivate Paint moneyBrokenCircle;//金额的折线点的圈的Paintprivate Paint timesBrokenLine;//售房户数的折线private Paint timesBrokenCircle;//售房户数的折线的点的圈的Paintprivate Paint moneyTextPaint;//绘制金额的文字private Paint timesTextPaint;//绘制售房户数的文字/** * 与上面的Bitmap对应 */private Canvas viewBgCanvas;private Canvas viewLeftBgCanvas;//绘制底部的视图//滚动条,用来移动布局private Scroller mScroller;//设置滑动速度的private VelocityTracker mVelocityTracker;public MyTrendView(Context context) {super(context);initResource();mScroller = new Scroller(context);ViewConfiguration localViewConfiguration = ViewConfiguration.get(context);    minimumFlingVelocitx = localViewConfiguration.getScaledMinimumFlingVelocity();    maxmumFlingVelocitx = localViewConfiguration.getScaledMaximumFlingVelocity();}public MyTrendView(Context context, AttributeSet attrs) {super(context, attrs);initResource();mScroller = new Scroller(context);ViewConfiguration localViewConfiguration = ViewConfiguration.get(context);minimumFlingVelocitx = localViewConfiguration.getScaledMinimumFlingVelocity();maxmumFlingVelocitx = localViewConfiguration.getScaledMaximumFlingVelocity();}/** * 加载资源,初始化一些变量 */@SuppressWarnings("deprecation")public void initResource() {Resources localResources = getResources();density = localResources.getDisplayMetrics().density;virtical_graduate = localResources.getDrawable(R.drawable.virtical_graduate);//刻度线broken_divider = localResources.getDrawable(R.drawable.broken_divider);//绘制水平的虚线report_trend_abscissa_bg = localResources.getDrawable(R.drawable.report_trend_abscissa_bg);report_trend_abscissa_divider = localResources.getDrawable(R.drawable.report_trend_abscissa_divider);viewBgPaint = new Paint();rectShaderPaint = new Paint();rectShaderPaint.setStyle(Paint.Style.FILL);rectShaderPaint.setARGB(30, 26, 28, 33);monthPaint = new Paint();monthPaint.setAntiAlias(true);monthPaint.setColor(getResources().getColor(R.color.white));monthPaint.setTextAlign(Paint.Align.LEFT);monthPaint.setTextSize(14.0F * this.density);monthTextPaint = new Paint();monthTextPaint.setAntiAlias(true);monthTextPaint.setColor(getResources().getColor(R.color.white));monthTextPaint.setTextAlign(Paint.Align.LEFT);monthTextPaint.setTextSize(8.0F * this.density);yearTextPaint = new Paint();yearTextPaint.setAntiAlias(true);yearTextPaint.setColor(getResources().getColor(R.color.white));yearTextPaint.setTextAlign(Paint.Align.LEFT);yearTextPaint.setTextSize(7.8F * density);leftTextPaint = new Paint();leftTextPaint.setAntiAlias(true);leftTextPaint.setColor(-1);leftTextPaint.setTextAlign(Paint.Align.LEFT);leftTextPaint.setTextSize(7.0F * density);moneyTextPaint = new Paint();moneyTextPaint.setAntiAlias(true);moneyTextPaint.setTextSize(8.0F * this.density);moneyTextPaint.setStrokeWidth(5);moneyTextPaint.setColor(getResources().getColor(R.color.white));moneyTextPaint.setStyle(Paint.Style.FILL);moneyBrokenLine = new Paint();moneyBrokenLine.setAntiAlias(true);moneyBrokenLine.setStrokeWidth(5);moneyBrokenLine.setColor(getResources().getColor(R.color.orangered));moneyBrokenLine.setStyle(Paint.Style.FILL);moneyBrokenCircle = new Paint();moneyBrokenCircle.setColor(getResources().getColor(R.color.orangered));timesBrokenCircle = new Paint();timesBrokenCircle.setAntiAlias(true);timesBrokenCircle.setStyle(Paint.Style.FILL);timesBrokenCircle.setColor(getResources().getColor(R.color.lime));timesBrokenLine = new Paint();timesBrokenLine.setAntiAlias(true);timesBrokenLine.setStrokeWidth(5);timesBrokenLine.setColor(getResources().getColor(R.color.lime));timesTextPaint = new Paint();timesTextPaint.setAntiAlias(true);timesTextPaint.setStrokeWidth(5);timesTextPaint.setTextSize(8.0F * this.density);timesTextPaint.setColor(getResources().getColor(R.color.white));timesTextPaint.setStyle(Paint.Style.FILL);    viewBgCanvas = new Canvas();    viewLeftBgCanvas = new Canvas();    Date localDate = new Date();currentYear = (1900 + localDate.getYear());currentMonth = localDate.getMonth();limitYears[0] = this.currentYear + "年";limitYears[1] = (-1 + this.currentYear) + "年";limitYears[2] = (-2 + this.currentYear) + "年";for (int i = 0; i < 3; i++) {limitMoneys[i] = String.valueOf(1000 * (3 - i));limitSalesTimes[i] = (2 * (3 - i)) + "户";}}/** * 计算高度,宽度 */public void initMeage(){layout_width = getWidth();//得到自己的宽度pxlayout_height = getHeight();//得到自己的高度pxlayout_width_34 = (int)(0.75F * layout_width);virtical_graduate_width = dip2px(60);//一个虚拟刻度的高度virtical_graduate_right = dip2px(20);//第一个虚拟刻度距离顶部的高度int margeTop = Math.round(44.0F * density);//距离左边44dipint margeBottom = Math.round(42.0F * density);//竖直的虚拟刻度线距离右边42dip//6指的是竖直箭头也有宽度的,三角头右边一部分,整个箭头的一半是9pxvirtical_graduate_bottom = layout_height - margeBottom - 6;monthTextW = GetTextWH.getTextW("00", monthPaint);//计算出月份的数字的宽度monthTextH = GetTextWH.getTextH(monthPaint);//绘制的月份中央对齐刻度线float monthTextW = GetTextWH.getTextW("月", monthTextPaint);drawMonthTop = layout_height - (int)((monthTextW + margeBottom + monthTextW) / 2.0F);drawYearTop = layout_height - (int)((virtical_graduate_bottom + GetTextWH.getTextW("2013年", yearTextPaint)) / 2F);scroll_top = margeTop + 8;scroll_bottom = virtical_graduate_bottom - 6;scroll_height = scroll_bottom;broken_divider_height = scroll_height / 4;//分为4份viewBottomBgBitmap_left = margeBottom;calculateActualWidth();initViewBg();initViewBottomBg();}/** *更新数据  *将数据进行处理,计算每一个点距离布局的左边位置 *然后重新绘制刻度等图像,调用 drawScrollStaticView() *@param maxMoney  最大的售房金额 *@param maxTimes  最大的售房户数 */public void updateData(ArrayList<ModelInfo> lists, double maxMoney, int maxTimes){actualMonths = lists.size();modelInfos = new ModelInfo[actualMonths];//RangMaxMoney  返回刻度的最高金额int RangMaxMoney = DataAccuracy.FomatMoneyString(limitMoneys, maxMoney);//RangMaxTimes 返回刻度的最大户数int RangMaxTimes = DataAccuracy.FomatTimesString(limitSalesTimes, maxTimes);for (int i = 0; i < actualMonths; i++) {ModelInfo modelInfo = lists.get(i);float test = scroll_height - (float)(modelInfo.getSaleMoney() / RangMaxMoney * scroll_height);float testTimes = scroll_height - (float)((double)modelInfo.getSaleCount() / RangMaxTimes * scroll_height);modelInfo.setSaleMoneyTop(test);modelInfo.setSaleHouseCountTop(testTimes);modelInfos[i] = modelInfo;}drawScrollStaticView();invalidate();}/** * 计算实际的view的高度 */public void calculateActualWidth(){view_width = (1+countMonths) * virtical_graduate_width;scroll_width = countMonths * virtical_graduate_width + virtical_graduate_right - layout_width;}/** * 主要绘制可以滚动的,但是内容不变的 * 刻度 * 月份,年 * 水平的虚线 * 竖直的阴影矩形 * 当数据不为空的时候,绘制折线 */public void drawScrollStaticView(){//清除屏幕viewBgCanvas.drawColor(0, PorterDuff.Mode.CLEAR);float timesTop = scroll_bottom;float scrollTimesTop = scroll_bottom;float moneyTop = scroll_bottom;float scrollMoneyTop = scroll_bottom;int drawMonth = currentMonth;int rightMarg = view_width - virtical_graduate_right;float yearWidth = GetTextWH.getTextW("0000年", yearTextPaint);//绘制水平月份刻度,总共countMonths个刻度for (int i = 0; i < countMonths; i++) {int temp = rightMarg - virtical_graduate_width;virtical_graduate.setBounds(temp, scroll_bottom, rightMarg, 10+scroll_bottom);virtical_graduate.draw(viewBgCanvas);//绘制月份数字        viewBgCanvas.drawText(monthArrays[drawMonth], temp-monthTextW / 2, scroll_bottom + monthTextH+16, monthPaint);//绘制月份数字        //绘制月份的月        viewBgCanvas.drawText("月", temp - monthTextW / 4, scroll_bottom + monthTextH * 2 + 16, monthTextPaint);//绘制月        //绘制年        if (drawMonth == 0 && i < countMonths) {        viewBgCanvas.drawText(limitYears[(i / 12)], temp-yearWidth / 2, scroll_bottom + monthTextH * 3+16, yearTextPaint);}      //绘制水平的虚线        int virtical_bottom = broken_divider_height;//虚线竖直高度        for (int j = 0; j < 3; j++) {        broken_divider.setBounds(temp, virtical_bottom, rightMarg, 2+virtical_bottom);        broken_divider.draw(viewBgCanvas);        virtical_bottom += broken_divider_height;}                //绘制竖直的阴影矩形        if ((i & 0x1) == 0)        viewBgCanvas.drawRect(temp, 6, rightMarg, scroll_bottom, rectShaderPaint);                //当数据不为空的时候绘制数据折线图        if (modelInfos != null) {if (i > actualMonths-1) {//当绘制的月数大约时间的月数的时候timesTop = scroll_bottom;moneyTop = scroll_bottom;scrollTimesTop = scroll_bottom;scrollMoneyTop = scroll_bottom;drawBrokenLine(viewBgCanvas,"", timesTop, scrollTimesTop, "",moneyTop, scrollMoneyTop, temp);}else if (i == actualMonths -1) {timesTop = modelInfos[i].getSaleHouseCountTop();moneyTop = modelInfos[i].getSaleMoneyTop();scrollTimesTop = scroll_bottom;scrollMoneyTop = scroll_bottom;drawBrokenLine(viewBgCanvas,modelInfos[i].getSaleHouseCountText(), timesTop, scrollTimesTop, "¥"+modelInfos[i].getSaleMoney(), moneyTop, scrollMoneyTop, temp);}else if (i < actualMonths-1) {timesTop = modelInfos[i].getSaleHouseCountTop();moneyTop = modelInfos[i].getSaleMoneyTop();scrollTimesTop = modelInfos[i+1].getSaleHouseCountTop();scrollMoneyTop = modelInfos[i+1].getSaleMoneyTop();drawBrokenLine(viewBgCanvas, modelInfos[i].getSaleHouseCountText(), timesTop, scrollTimesTop, "¥"+modelInfos[i].getSaleMoney(), moneyTop, scrollMoneyTop, temp);}}                int drawMonth_1 = drawMonth -1;        if (drawMonth_1 < 0) {        drawMonth_1 = 11;}        drawMonth = drawMonth_1;        rightMarg = temp;}viewLeftBgCanvas.drawColor(0, PorterDuff.Mode.CLEAR);drawScrollStaticLeftView(viewLeftBgCanvas);}private void drawBrokenLine(Canvas canvas, String times, float timesTop, float scrollTimesTop, String money, float moneyTop, float scrollMoneyTop, int rightMarg){canvas.save();canvas.drawCircle(rightMarg, timesTop, 6, timesBrokenLine);canvas.drawLine(rightMarg, timesTop, rightMarg-virtical_graduate_width, scrollTimesTop, timesBrokenLine);canvas.drawCircle(rightMarg, moneyTop, 6, moneyBrokenCircle);canvas.drawLine(rightMarg, moneyTop, rightMarg-virtical_graduate_width, scrollMoneyTop, moneyBrokenLine);canvas.drawText(times, rightMarg-15, timesTop, timesTextPaint);canvas.drawText(money, rightMarg, moneyTop-5, moneyTextPaint);}/** * 绘制左侧的销售金额和销售的房数 * @param canvas */private void drawScrollStaticLeftView(Canvas canvas){report_trend_abscissa_bg.setBounds(0, 0, virtical_graduate_width, scroll_height);report_trend_abscissa_bg.draw(canvas);float textH = GetTextWH.getTextH(leftTextPaint);float textMoneyLeft = 4F * density;int bottom_marge = broken_divider_height;for (int i = 0; i < 3; ++i){report_trend_abscissa_divider.setBounds(0, bottom_marge, virtical_graduate_width, bottom_marge + 2);report_trend_abscissa_divider.draw(canvas);canvas.drawText(limitMoneys[i], textMoneyLeft, bottom_marge + textH*3/2, leftTextPaint);canvas.drawText(limitSalesTimes[i], textMoneyLeft, bottom_marge + textH * 5/2 + 6, leftTextPaint);bottom_marge += broken_divider_height;    }}/** * 初始化主体的位图,并绑定canvas * 这样绘制在canvas上面图像映射到位图上面 */public void initViewBg(){viewBgBitmap = Bitmap.createBitmap(view_width, layout_height, Bitmap.Config.ARGB_8888);viewBgCanvas.setBitmap(viewBgBitmap);}/** * 绘制左部位图,并绑定canvas */public void initViewBottomBg(){viewLeftBgBitmap = Bitmap.createBitmap(virtical_graduate_width, scroll_height, Bitmap.Config.ARGB_8888);viewLeftBgCanvas.setBitmap(viewLeftBgBitmap);}private void obtainVelocityTracker(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);}private void releaseVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawBitmap(viewBgBitmap, -view_width+layout_width-virtical_graduate_right, 0, viewBgPaint);//绘制左边的部分,由于要固定在左边,所以x设置为getScrollX(),不然移动的canvas.drawBitmap(viewLeftBgBitmap, getScrollX(), 0, viewBgPaint);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right,int bottom) {super.onLayout(changed, left, top, right, bottom);if (!isNotFirst) {//只需要执行一次就可以了initMeage();drawScrollStaticView();isNotFirst = true;}}/** * dip 与 px 换算 *  * @param dipValue * @return */public int dip2px(float dipValue) {return (int) (dipValue / 1.5F * density + 0.5);}@Overridepublic void computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(),  mScroller.getCurrY());}super.computeScroll();}@Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);float x = event.getX();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mLastMotionX = x;//动画还没有结束就按下了就结束动画if (!mScroller.isFinished()) {mScroller.abortAnimation();}break;case MotionEvent.ACTION_MOVE:int deltaX = (int) (mLastMotionX - x);mLastMotionX = x;if (getScrollX() < layout_width_34 && getScrollX() > -(layout_width_34+scroll_width)) {scrollBy(deltaX, 0);}invalidate();break;case MotionEvent.ACTION_UP:if (getScrollX() > 0 || view_width <= layout_width) {mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 500);}else if (getScrollX() < -scroll_width) {mScroller.startScroll(getScrollX(), 0, -getScrollX()-scroll_width, 0, 500);}invalidate();break;}return true;}public void myReleaseFling(int speed){mScroller.fling(getScrollX(), 0, speed, 0, 0, scroll_width, 0, 0);}}

6.7、实现的效果:


左边显示内容,是动态变化的,根据你最大值来处理。








1 0