彩票走势图
前几天在做一个彩票的项目的时候遇到了彩票的走势图这个功能,网上查了没有,就自己写了个,现在和大家分享下,以方便后来做彩票走势图的同学们。
先分析下,走势图肯定是很大的一块,所以需要横向和纵向都能够通过手势来滑动,所以大家很快就能想到一个纵向的ScrollView嵌套一个横向的HorizontalScrollView就能够实现,这个是最外面的一层。如若想在对应的数字下面画球,大家肯定就想到了需要知道每个球的(x,y),走势图的期数一定的情况下,就可以设定走势图的高度,走势图的高度了定了,Y轴需要被分为多少份也定了,他和期数是一样的,x轴的需要分的份数是一定的,比如双色球的红球就分为33份,篮球就分为16份,所以每个球的x,y坐标就能被计算出来。
我给大家将走势图的代码贴出来,同时我做了个demo,喜欢省事的盆友们直接下载就好了。
这个是走势图的代码
<!-- lang: java -->
public class TrendView extends View implements Observer{
public static int[][] TestRowRed = new int[][]{
{6,14,18,22,28,29},
{7,3,17,20,24,33},
{3,19,11,24,27,30},
{7,14,12,26,28,30},
{1,2,7,20,22,29},
{1,5,17,18,21,23},
{2,4,7,22,23,29},
{3,4,5,7,21,30},
{6,14,18,22,28,21},
{3,4,7,26,28,29}
};
public static int[] TestRowBlue = new int[]{3,12,11,24,27,33,5,23,2,31};
static final int kCopies_X = 33;//x 轴分多少份
static final int kCopies_Y = 10;//y 轴分多少份
float mDelt_X ;//x 轴每个坐标间的跨度,单位像素,根据实际view大小计算
float mDelt_Y ;//y 轴每个坐标间的跨度,单位像素,根据实际view大小计算
int countYLine = 10;//y轴的数量
//顶部的空白,用于填数字,单位dip
static final float kTopPadding_Y = 24.0f;
float mPadding_top;
private Matrix mMatrix = new Matrix();
public PaintFlagsDrawFilter mPaintFlagsDrawFilter;// 毛边过滤
/**
* 画笔:线
*/
private Paint mLinePaint = new Paint();
static final float kFontSize = 13;
float mFontSize;
/**
* 画笔:圆
*/
private Paint mCirclePaint = new Paint();
/**
* view的宽高
*/
private int mWidth;
private int mHeight;
Context mContext;
/**
* 画笔:球
*/
private Paint mBallPaint = new Paint();
private Drawable mCricleDrawable;
private Bitmap mCricleBmp;
boolean isNeedLink = false;//是否需要在ball之间连线
int lineColor;//连线的颜色
//
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
};
};
//用于自动刷新
Runnable freshRunnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
invalidate();
handler.postDelayed(this, 500);
}
};
/**
* 一些初始化方法
*/
public void init(Context context,AttributeSet attrs){
this.mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TrendView);
int resId = a.getResourceId(R.styleable.TrendView_ballImage, R.drawable.ball_red);
isNeedLink = a.getBoolean(R.styleable.TrendView_isNeedLinkEveryBall, false);
lineColor = a.getColor(R.styleable.TrendView_lineColor, Color.RED);
a.recycle();
this.mPaintFlagsDrawFilter = new PaintFlagsDrawFilter(0,
Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
this.mCricleDrawable = getResources().getDrawable(
R.drawable.ball_red);
this.mCirclePaint.setTextSize(25);
this.mCirclePaint.setTextScaleX(1.5f);
this.mLinePaint.setStrokeWidth(2);
this.mCricleBmp = BitmapFactory.decodeResource(getResources(), resId);
this.handler.postDelayed(freshRunnable, 500);
}
public TrendView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init(context,attrs);
}
public TrendView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init(context,attrs);
}
@Override
public void update(Observable observable, Object data) {
// TODO Auto-generated method stub
Log.i("observer", "observable >"+observable);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.setDrawFilter(mPaintFlagsDrawFilter);
canvas.drawColor(Color.WHITE);
drawTopNum(canvas);
drawXLine(canvas);
// drawYLine(canvas);
drawRightVerticalLine(canvas);
drawBallLinkLine(canvas);
drawBall(canvas);
}
/**
* 画顶部的数字和开始横线
* @param cns
*/
void drawTopNum(Canvas cns) {
if (mPadding_top <=0) {
return;
}
mLinePaint.setStrokeWidth(1);
mLinePaint.setColor(Color.GRAY);
mLinePaint.setTextSize(mFontSize);
cns.drawLine(0, mPadding_top, this.mWidth, mPadding_top, mLinePaint);
mLinePaint.setColor(Color.RED);
for (int i = 0;i < kCopies_X;i ++) {
//写字符时,加点偏移
if (i >= 9) {
cns.drawText(String.valueOf(i+1), this.mDelt_X* i + (int)(this.mDelt_X * 0.1f) ,0 + mPadding_top * 0.7f , mLinePaint);
}else {
cns.drawText(String.valueOf(i+1), this.mDelt_X* i + (int)(this.mDelt_X * 0.4f) ,0 + mPadding_top * 0.7f, mLinePaint);
}
}
}
/**
* 画y轴,--->
* @param canvas
*/
void drawXLine(Canvas cns) {
mLinePaint.setStrokeWidth(1);
mLinePaint.setColor(Color.GRAY);
for (int i = 0;i < kCopies_Y;i ++) {
cns.drawLine(0, this.mDelt_Y* (i + 1) + mPadding_top, this.mWidth, this.mDelt_Y* (i + 1) + mPadding_top, mLinePaint);
}
}
/**
* 画最右边的横线
* @param canvas
*/
void drawRightVerticalLine(Canvas cns) {
mLinePaint.setStrokeWidth(4);
mLinePaint.setColor(Color.GRAY);
cns.drawLine(mWidth ,0 + mPadding_top , mWidth,this.mHeight, mLinePaint);
}
/**
* 画X轴, ^|
* @param canvas
*/
void drawYLine(Canvas cns) {
mLinePaint.setStrokeWidth(1);
mLinePaint.setColor(Color.GREEN);
for (int i = 0;i < kCopies_X;i ++) {
cns.drawLine(this.mDelt_X* (i + 1),0 + mPadding_top , this.mDelt_X* (i + 1),this.mHeight, mLinePaint);
}
}
void drawBallLinkLine(Canvas cns) {
cns.setDrawFilter(mPaintFlagsDrawFilter);
mLinePaint.setStrokeWidth(2);
mLinePaint.setColor(lineColor);
if (isNeedLink) {
float[] lastXy = this.translateRowIndex2XY(0, TestRowBlue[0] - 1);
for (int i = 1 ;i < TestRowBlue.length;i ++) {
int value = TestRowBlue[i];
float[] xy = this.translateRowIndex2XY(i, value - 1);
cns.drawLine(lastXy[0]+ mDelt_X * 0.5f,lastXy[1]+ mDelt_Y * 0.5f , xy[0]+ mDelt_X * 0.5f,xy[1]+ mDelt_Y * 0.5f , mLinePaint);
lastXy[0] = xy[0];
lastXy[1] = xy[1];
}
}
}
void drawBall(Canvas cns) {
mLinePaint.setColor(Color.WHITE);
mLinePaint.setTextSize(mFontSize - 4);//比顶部字体小点
Rect src = new Rect();
Rect dst = new Rect();
if (isNeedLink) {
for (int i = 0 ;i < TestRowBlue.length;i ++) {
int value = TestRowBlue[i];
float[] xy = this.translateRowIndex2XY(i, value - 1);
src.left = 0;
src.top = 0;
src.bottom = (int) mCricleBmp.getHeight();
src.right = (int)mCricleBmp.getWidth();
dst.left = (int) (xy[0] + this.mDelt_X * 0.15f);
dst.top = (int)(xy[1]+ this.mDelt_Y * 0.15f);
dst.bottom = (int) (dst.top + mDelt_Y * 0.8f);
dst.right = (int) (dst.left + mDelt_X * 0.8f);
//在对应的位置画球球
cns.drawBitmap(mCricleBmp, src, dst, this.mBallPaint);
if (value >= 9) {
cns.drawText(String.valueOf(value), dst.left + (int)(this.mDelt_X * 0.1f) ,dst.top+ this.mDelt_Y * 0.5f , mLinePaint);
}else {
cns.drawText(String.valueOf(value ), dst.left + (int)(this.mDelt_X * 0.2f) ,dst.top+ this.mDelt_Y * 0.5f , mLinePaint);
}
}
} else {
//just for test
//row1
for (int i = 0;i < TestRowRed.length; i ++) {
int[] testRow = TestRowRed[i];
for (int j = 0;j < testRow.length; j ++) {
int xIndex = testRow[j] ;
float[] xy = this.translateRowIndex2XY(i, xIndex - 1);
src.left = 0;
src.top = 0;
src.bottom = (int) mCricleBmp.getHeight();
src.right = (int)mCricleBmp.getWidth();
//缩小球
dst.left = (int) (xy[0] + this.mDelt_X * 0.15f);
dst.top = (int)(xy[1]+ this.mDelt_Y * 0.15f);
dst.bottom = (int) (dst.top + mDelt_Y * 0.8f);
dst.right = (int) (dst.left + mDelt_X * 0.8f);
//在对应的位置画球球
cns.drawBitmap(mCricleBmp, src, dst, this.mBallPaint);
if (xIndex >= 9) {
cns.drawText(String.valueOf(xIndex), dst.left + (int)(this.mDelt_X * 0.1f) ,dst.top+ this.mDelt_Y * 0.5f , mLinePaint);
}else {
cns.drawText(String.valueOf(xIndex ), dst.left + (int)(this.mDelt_X * 0.2f) ,dst.top+ this.mDelt_Y * 0.5f , mLinePaint);
}
}
}
}
}
/**
* 根据位置行和列,转成对应的x,y坐标,不做校正,即都返回左上角坐标
* @param rowIndex 所在行,从0开始
* @param xIndex 所在列,从0开始
* @return [x0,y0]
*/
float[] translateRowIndex2XY (int rowIndex,int xIndex) {
float[] xy = new float[2];
xy[0] = this.mDelt_X* xIndex ;
xy[1] = this.mPadding_top + this.mDelt_Y * rowIndex;
return xy;
}
/**
* 根据view的宽高,初始化部分尺寸
* @param w
* @param h
*/
void setViewSize(int w,int h) {
this.mWidth = w;
this.mHeight = h;
this.mDelt_X = 1.0f*this.mWidth /kCopies_X;
this.mPadding_top = (float) DimensionPixelUtil.dip2px(mContext, kTopPadding_Y);
//减去顶部的预留空白后,再均分
this.mDelt_Y = 1.0f*(this.mHeight - mPadding_top)/kCopies_Y;
this.mFontSize = DimensionPixelUtil.sp2px(mContext, kFontSize);
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
System.out.println("onMeasure---widthMeasureSpec::"+widthMeasureSpec +" heightMeasureSpec::"+heightMeasureSpec);
int measuredHeight = measureHeight(heightMeasureSpec);
int measuredWidth = measureWidth(widthMeasureSpec);
System.out.println("onMeasure---measuredWidth::"+measuredWidth +" measuredHeight::"+measuredHeight);
setMeasuredDimension(measuredWidth,measuredHeight);
this.setViewSize(getMeasuredWidth(),getMeasuredHeight());
}
//如果是AT_MOST,specSize 代表的是最大可获得的空间;
//如果是EXACTLY,specSize 代表的是精确的尺寸;
//如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
private int measureHeight(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
System.out.println("measureHeight----specMode::"+specMode +" specSize::"+specSize);
// Default size if no limits are specified.
int result = 100;
if (specMode == MeasureSpec.AT_MOST) {
// Calculate the ideal size of your
// control within this maximum size.
// If your control fills the available
// space return the outer bound.
result = specSize;
} else if (specMode == MeasureSpec.EXACTLY) {
// If your control can fit within these bounds return that value.
result = specSize;
}
return result;
}
private int measureWidth(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
System.out.println("measureWidth----specMode::"+specMode +" specSize::"+specSize);
// Default size if no limits are specified.
int result = 500;
if (specMode == MeasureSpec.AT_MOST) {
// Calculate the ideal size of your control
// within this maximum size.
// If your control fills the available space
// return the outer bound.
result = specSize;
}
else if (specMode == MeasureSpec.EXACTLY) {
// If your control can fit within these bounds return that value.
result = specSize;
}
return result;
}
}
这个是xml文件的代码
<!-- lang: java -->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:trendview="http://schemas.android.com/apk/res/com.example.caipiaotrendchart" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#F4F2EE" android:orientation="vertical" >
<ScrollView
android:id="@+id/scrollview_fucai"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<HorizontalScrollView
android:id="@+id/synchor_double_red"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:scrollbars="none" >
<LinearLayout
android:id="@+id/linear_setheight"
android:layout_width="wrap_content"
android:layout_height="314dip"
android:orientation="horizontal" >
<!-- 走势图 -->
<RelativeLayout
android:id="@+id/re_red_trend"
android:layout_width="893dip"
android:layout_height="fill_parent"
>
<View
android:id="@+id/v1"
android:layout_width="1dip"
android:layout_height="fill_parent"
android:layout_marginTop="24dip"
android:background="@android:color/darker_gray" />
<com.example.caipiaotrendchart.TrendView
android:id="@+id/trendview_double_a"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/v1"
android:paddingBottom="5dip"
android:paddingTop="5dip"
trendview:ballImage="@drawable/ball_red"
trendview:lineColor="#ffff0000" />
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:layout_below="@id/trendview_double_a"
android:background="@android:color/darker_gray" />
<!-- 装图的 -->
</RelativeLayout>
<RelativeLayout
android:id="@+id/re_blue"
android:layout_width="893dip"
android:layout_height="fill_parent" >
<com.example.caipiaotrendchart.TrendView
android:id="@+id/trendview_double_b"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:paddingBottom="5dip"
android:paddingTop="5dip"
trendview:ballImage="@drawable/ball_blue"
trendview:isNeedLinkEveryBall="true"
trendview:lineColor="#1699F5" />
<!-- 装图的 -->
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:layout_below="@id/trendview_double_b"
android:background="@android:color/darker_gray" />
</RelativeLayout>
</LinearLayout>
</HorizontalScrollView>
</ScrollView>
</RelativeLayout>
这个是那个工具类的代码
<!-- lang: java -->
public class DimensionPixelUtil {
public final static int PX = TypedValue.COMPLEX_UNIT_PX;
public final static int DIP = TypedValue.COMPLEX_UNIT_DIP;
public final static int SP = TypedValue.COMPLEX_UNIT_SP;
/**
*
* @param unit
* 单位 </br>0 px</br>1 dip</br>2 sp
* @param value
* size 大小
* @param context
* @return
*/
public static float getDimensionPixelSize(int unit, float value,
Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
switch (unit) {
case PX:
return value;
case DIP:
case SP:
return TypedValue.applyDimension(unit, value, metrics);
default:
throw new IllegalArgumentException("unknow unix");
}
}
/**
* 根据手机的屏幕属性从 dip 的单位 转成为 px(像素)
*
* @param context
* @param value
* @return
*/
public static float dip2px(Context context, float value) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return value * metrics.density;
}
/**
* 根据手机的屏幕属性从 px(像素) 的单位 转成为 dip
*
* @param context
* @param value
* @return
*/
public static float px2dip(Context context, float value) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return value / metrics.density;
}
}
这个是mainactivity代码
<!-- lang: java -->
public class MainActivity extends Activity {
TrendView trendViewRedBall;
TrendView trendViewBlueBall;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
trendViewRedBall = (TrendView) findViewById(R.id.trendview_double_a);
trendViewBlueBall = (TrendView) findViewById(R.id.trendview_double_b);
}
}
走势图的高度和宽度是可以动态设定的,有兴趣的同学可以自己添加下,另外y轴分多少份也可以动态设定,大家只需要将 成员变量(static final int kCopies_Y = 10;//y 轴分多少份 ) 这个行代码的static final去掉,然后 设置kCopies_Y的get set方法,在mainactivity中set就可以了。
从这里下载代码 http://www.oschina.net/code/snippet_724007_34027 这个是效果图
有什么问题欢迎大家指教