美文网首页
2018-03-01

2018-03-01

作者: 拾取时光 | 来源:发表于2018-03-13 15:51 被阅读0次

                  Android开发之实现比特币走势图(仿股票走势图)

    1、介绍

           最近有关区块链的项目层出不穷,在项目中碰到了绘制比特币涨幅走势图的需求,在网上搜了一些案例,大致就是利用绘制贝塞尔曲线来完成。

    2、思路

    思路来源于绘制股票走势图,主要内容在于自定义K_View,效果图:

    大致步骤:

    1、绘制X、Y坐标轴并绘制坐标;

    2、根据数据绘制走势图,其实就是根据返回数据的多个点连成的平滑曲线;

    3、根据手势找点,然后绘制当前所在点并显示具体数据。

    相关数据对象类

    public class FundMode  implements Serializable {

    //x轴原始时间数据,ms

        public Stringtime;

    public Stringlast;

    public float dataY;

    //在自定义view:FundView中的位置坐标

        public float floatX;

    public float floatY;

    }

    public class XYEntityimplements Serializable{

    public Listxlist;

    public Listylist;

    }

    这里的内容只是让一些小白们看的更容易理解,早对象可以根据需求随意改动,各取所需。

    Xml布局中代码

    <XXXXX....K_VIew

        android:id="@+id/k_view"

        android:layout_width="match_parent"

        android:layout_height="@dimen/base130dp"

        app:xyTextSize="@dimen/base8sp"

        app:loadingTextSize="@dimen/base12sp"

        app:longPressTextSize="@dimen/base10sp"

        app:loadingText="正在加载数据...."

        app:xBottomTopPadding="@dimen/base8dp"

        app:reactWidth="@dimen/base100dp"

        app:reactHeight="@dimen/base38dp"

        app:reactTextMargin_top="@dimen/base8dp"

        app:reactTextMargin_bottom="@dimen/base23dp"

        app:paddingTop_FV="@dimen/base10dp"

        app:paddingBottom_FV="@dimen/base20dp"

        app:paddingRight_FV="@dimen/base10dp"

        app:yTextPadding="@dimen/base10dp"

        app:yTextRightPadding="@dimen/base5dp"

        android:background="#fff"/>

    attrs.xml 中内容大致就是一些自定义属性,内容如下


    activity 中需要执行的操作很少,从服务器获取到数据后 直接调用 k_view.setData(apiResult)即可;


    下面是自定义view具体代码,所有逻辑都包括在里面了

    public class K_View  extends View {

    //控件默认宽高

    private static final float DEF_WIDTH =650;

    private static final float DEF_HIGHT =400;

    //数据源

      ApiResult2<List< FundMode>,XYEntity>>apiResult;

    //ApiResult2 服务器返回的数据,FundMode平滑曲线上的点,XYEntity X、Y对应坐标,下面贴出数据结构


    //控件宽高

        int mWidth;

    int mHeight;

    //上下左右padding

        int mPaddingTop =30;

    int mPaddingBottom =50;

    int yTextPadding=20;

    float mPaddingLeft =70;

    int mPaddingRight =30;

    int yTextRightPadding =5;

    float textTopPadding=5;

    int reactHeight=80;

    int reactWidth=210;

    int reactTextMargin_top=20;

    int reactTextMargin_bottom=50

       FundMode mMinFundMode;

    FundMode mMaxFundMode;

    float maxY;

    float minY;

    //X、Y轴每一个data对应的大小

        float mPerX;

    float mPerY;

    //正在加载中

        Paint mLoadingPaint;

    int mLoadingTextSize =20;

    String mLoadingText ="";

    boolean mDrawLoadingPaint =true;

    Paint xyPaint;

    //外围X、Y轴线文字

        Paint mXYPaint;

    Paint longPressPaint;

    //x、y轴指示文字字体的大小

        Paint rectTextPaint;

    private int mXYTextSize =20;

    //左侧文字距离左边线线的距离

        final float mLeftTxtPadding =5;

    //底部文字距离底部线的距离

        int xBottomTopPadding =20;

    float halfTextWidth_X=22;

    private ListpointData=new ArrayList<>();

    private int pointCount;

    //内部X轴虚线

        Paint mInnerXPaint;

    float mInnerXStrokeWidth =1;

    //折线

        Paint mBrokenPaint;

    Paint rectBGPaint;

    Paint alphaPaint;

    //单位:dp

    //长按的十字线

        Paint mLongPressPaint;

    Paint blueLinePaint;

    boolean mDrawLongPressPaint =false;

    //长按处理

        long mPressTime;

    //默认多长时间算长按

        final long DEF_LONGPRESS_LENGTH =200;

    float mPressX;

    float mPressY;

    //最上面默认显示累计收益金额

        final float mDefAllIncomeTextSize =20;

    //长按情况下x轴和y轴要显示的文字

        Paint mLongPressTxtPaint;

    int mLongPressTextSize =25;

    public K_View(Context context) {

    this(context,null);

    }

    public K_View(Context context, @Nullable AttributeSet attrs) {

    this(context, attrs,0);

    }

    public K_View(Context context, @Nullable AttributeSet attrs,int defStyleAttr) {

    super(context, attrs, defStyleAttr);

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.K_View);

    mXYTextSize=a.getDimensionPixelSize(R.styleable.K_View_xyTextSize, mXYTextSize);

    mLoadingTextSize=a.getDimensionPixelSize(R.styleable.K_View_loadingTextSize, mLoadingTextSize);

    mLongPressTextSize=a.getDimensionPixelSize(R.styleable.K_View_longPressTextSize, mLongPressTextSize);

    mLoadingText=a.getString(R.styleable.K_View_loadingText);

    xBottomTopPadding=a.getDimensionPixelSize(R.styleable.K_View_xBottomTopPadding, xBottomTopPadding);

    reactWidth=a.getDimensionPixelSize(R.styleable.K_View_reactWidth, reactWidth);

    reactHeight=a.getDimensionPixelSize(R.styleable.K_View_reactHeight, reactHeight);

    reactTextMargin_top=a.getDimensionPixelSize(R.styleable.K_View_reactTextMargin_top, reactTextMargin_top);

    reactTextMargin_bottom=a.getDimensionPixelSize(R.styleable.K_View_reactTextMargin_bottom, reactTextMargin_bottom);

    mPaddingTop=a.getDimensionPixelSize(R.styleable.K_View_paddingTop_FV, mPaddingTop);

    mPaddingBottom=a.getDimensionPixelSize(R.styleable.K_View_paddingBottom_FV, mPaddingBottom);

    mPaddingRight=a.getDimensionPixelSize(R.styleable.K_View_paddingRight_FV, mPaddingRight);

    yTextPadding=a.getDimensionPixelSize(R.styleable.K_View_yTextPadding, yTextPadding);

    yTextRightPadding =a.getDimensionPixelSize(R.styleable.K_View_yTextRightPadding, yTextRightPadding);

    initAttrs();

    }

    @Override

    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (widthSpecMode == AT_MOST && heightSpecMode == AT_MOST) {

    setMeasuredDimension((int) DEF_WIDTH, (int) DEF_HIGHT);

    }else if (widthSpecMode == AT_MOST) {

    setMeasuredDimension((int) DEF_WIDTH, heightSpecSize);

    }else if (heightSpecMode == AT_MOST) {

    setMeasuredDimension(widthSpecSize, (int) DEF_HIGHT);

    }else {

    setMeasuredDimension(widthSpecSize, heightSpecSize);

    }

    mWidth = getMeasuredWidth();

    mHeight = getMeasuredHeight();

    }

    @Override

    protected void onLayout(boolean changed,int left,int top,int right,int bottom) {

    super.onLayout(changed, left, top, right, bottom);

    }

    @Override

    protected void onDraw(Canvas canvas) {

    super.onDraw(canvas);

    //默认加载loading界面

            showLoadingPaint(canvas);

    if (apiResult ==null || apiResult.data.size() ==0)return;

    drawInnerXPaint(canvas);

    drawBrokenPaint(canvas);

    drawXYPaint(canvas);

    drawLongPress(canvas);

    }

    @Override

    public boolean onTouchEvent(MotionEvent event) {

    switch (event.getAction()) {

    case MotionEvent.ACTION_DOWN:

    //                mPressTime = event.getDownTime();

                    mPressX = event.getX();

    mPressY = event.getY();

    //处理长按后的逻辑

                    showLongPressView();

    break;

    case MotionEvent.ACTION_MOVE:

    mPressX = event.getX();

    mPressY = event.getY();

    //处理长按后的逻辑

                        showLongPressView();

    //                }

                    break;

    case MotionEvent.ACTION_UP:

    //处理松手后的逻辑

                    hiddenLongPressView();

    break;

    default:

    break;

    }

    return true;

    }

    private void initAttrs() {

    intXYLintPaint();

    initRectBGPaint();

    initLoadingPaint();

    initRectTextPaint();

    initInnerXPaint();

    initXYPaint();

    initBrokenPaint();

    initAlphaPaint();

    initLongPressPaint();

    initYLinePaint();

    //        initTopTxt();

        }

    private void intXYLintPaint() {

    xyPaint =new Paint();

    xyPaint.setColor(getColor(R.color.gray));

    xyPaint.setStrokeWidth(mInnerXStrokeWidth);

    }

    private void initRectBGPaint() {

    rectBGPaint = getRectBGPaint();

    }

    private void initLoadingPaint() {

    mLoadingPaint =new Paint();

    mLoadingPaint.setColor(getColor(R.color.gray));

    mLoadingPaint.setTextSize(mLoadingTextSize);

    mLoadingPaint.setAntiAlias(true);

    }

    //初始化绘制虚线的画笔

        private void initInnerXPaint() {

    mInnerXPaint =new Paint();

    mInnerXPaint.setColor(getColor(R.color.bg_lsj));

    mInnerXPaint.setStrokeWidth(mInnerXStrokeWidth);

    mInnerXPaint.setAlpha(40);

    mInnerXPaint.setStyle(Paint.Style.STROKE);

    }

    private void initXYPaint() {

    mXYPaint =new Paint();

    mXYPaint.setColor(getColor(R.color.text_dz));

    mXYPaint.setTextSize(mXYTextSize);

    mXYPaint.setAntiAlias(true);

    }

    private void initLongPressTextPaint() {

    longPressPaint =new Paint();

    longPressPaint.setColor(getColor(R.color.text_dz));

    longPressPaint.setTextSize(mXYTextSize);

    longPressPaint.setAntiAlias(true);

    }

    private void initRectTextPaint() {

    rectTextPaint =new Paint();

    rectTextPaint.setColor(getColor(R.color.white));

    rectTextPaint.setTextSize(mXYTextSize);

    rectTextPaint.setAntiAlias(true);

    }

    private void initBrokenPaint() {

    mBrokenPaint =new Paint();

    mBrokenPaint.setColor(getColor(R.color.all_k_color));

    mBrokenPaint.setStyle(Paint.Style.STROKE);

    mBrokenPaint.setAntiAlias(true);

    mBrokenPaint.setStrokeWidth(convertDp2Px(mInnerXStrokeWidth));

    }

    private void initAlphaPaint() {

    alphaPaint =new Paint();

    alphaPaint.setColor(getResources().getColor(R.color.all_k_color));

    alphaPaint.setAntiAlias(true);

    alphaPaint.setAlpha(40);

    alphaPaint.setStyle(Paint.Style.FILL);

    }

    private void initLongPressPaint() {

    mLongPressPaint =new Paint();

    mLongPressPaint.setColor(getColor(R.color.white));

    mLongPressPaint.setStyle(Paint.Style.FILL);

    mLongPressPaint.setAntiAlias(true);

    mLongPressPaint.setTextSize(mLongPressTextSize);

    }

    private void initYLinePaint() {

    blueLinePaint =new Paint();

    blueLinePaint.setColor(getColor(R.color.bg_lsj));

    blueLinePaint.setStrokeWidth(1);

    blueLinePaint.setAntiAlias(true);

    }

    private void showLoadingPaint(Canvas canvas) {

    if (!mDrawLoadingPaint)return;

    //这里特别注意,x轴的起始点要减去文字宽度的一半

            canvas.drawText(mLoadingText, mWidth /2 - mLoadingPaint.measureText(mLoadingText) /2, mHeight /2, mLoadingPaint);

    }

    private void drawInnerXPaint(Canvas canvas) {

    //画5条横轴的虚线

    //首先确定最大值和最小值的位置

            float perHight = (mHeight - mPaddingBottom - mPaddingTop) /3;

    canvas.drawLine(mPaddingLeft,mPaddingTop,mPaddingLeft,mHeight - mPaddingBottom,xyPaint);

    canvas.drawLine(mPaddingLeft,mHeight - mPaddingBottom,mPaddingLeft+mPerX*(apiResult.data.size()-1),mHeight - mPaddingBottom,xyPaint);

    canvas.drawLine(0 + mPaddingLeft, mPaddingTop,

    mPaddingLeft+mPerX*(apiResult.data.size()-1), mPaddingTop, mInnerXPaint);//最上面的那一条

            canvas.drawLine(0 + mPaddingLeft, mPaddingTop + perHight *1,

    mPaddingLeft+mPerX*(apiResult.data.size()-1), mPaddingTop + perHight *1, mInnerXPaint);//2

            canvas.drawLine(0 + mPaddingLeft, mPaddingTop + perHight *2,

    mPaddingLeft+mPerX*(apiResult.data.size()-1), mPaddingTop + perHight *2, mInnerXPaint);//4

        }

    private void drawBrokenPaint(Canvas canvas) {

    if(pointData.size()>0){//清空所有点

                pointData.clear();

    }

    //先画第一个点

            FundMode fundMode = apiResult.data.get(0);

    Path path =new Path();

    //这里需要说明一下,x轴的起始点,其实需要加上mPerX,但是加上之后不是从起始位置开始,不好看。

    // 同理,for循环内x轴其实需要(i+1)。现在这样处理,最后会留一点空隙,其实挺好看的。

            float floatY = mHeight - mPaddingBottom - mPerY * ((Float.parseFloat(fundMode.last) - minY));

    fundMode.floatX = mPaddingLeft;

    fundMode.floatY = floatY;

    Path timeAlphaPath =new Path();

    timeAlphaPath.moveTo(mPaddingLeft, mHeight - mPaddingBottom);

    timeAlphaPath.lineTo(mPaddingLeft, floatY);

    for (int i =0; i < apiResult.data.size(); i++) {

    FundMode fm = apiResult.data.get(i);

    float floatX1 = mPaddingLeft + mPerX * i;

    float floatY1 = mHeight - mPaddingBottom - mPerY * ((Float.parseFloat(fm.last) - minY));

    fm.floatX = floatX1;

    fm.floatY = floatY1;

    if(i%pointCount==0||i==apiResult.data.size()-1){

    pointData.add(fm);

    }

    if(i

    float floatX2 = mPaddingLeft + mPerX * (i+1);

    float floatY2 = mHeight - mPaddingBottom - mPerY * ( (Float.parseFloat(apiResult.data.get(i+1).last) - minY));

    float wt = (floatX1 + floatX2) /2;

    float floatX1_last=wt;

    float floatY1_last =floatY1;

    float floatX2_next=wt;

    float floatY2_next = floatY2;

    if(i==0){

    path.moveTo(floatX1, floatY1);

    }

    path.cubicTo(floatX1_last, floatY1_last, floatX2_next, floatY2_next, floatX2, floatY2);

    timeAlphaPath.cubicTo(floatX1_last, floatY1_last, floatX2_next, floatY2_next, floatX2, floatY2);

    }else {

    timeAlphaPath.lineTo(floatX1 , mHeight - mPaddingBottom);

    Paint paint=new Paint();

    paint.setColor(getColor(R.color.bg_lsj));

    paint.setAntiAlias(true);

    paint.setStrokeWidth(1);

    paint.setStyle(Paint.Style.STROKE);

    canvas.drawPath(path, paint);

    paint.setAlpha(40);

    paint.setStyle(Paint.Style.FILL);

    canvas.drawPath(timeAlphaPath, paint);

    }

    }

    }

    private void drawXYPaint(Canvas canvas) {

    //先处理y轴方向文字

            drawYPaint(canvas);

    //处理x轴方向文字

            drawXPaint(canvas);

    }

    *

    * @param canvas

    */

    private void drawLongPress(Canvas canvas) {

    if (!mDrawLongPressPaint)return;

    //获取距离最近按下的位置的model

            float pressX = mPressX;

    //循环遍历,找到距离最短的x轴的mode

            FundMode finalFundMode = pointData.get(0);

    float minXLen = Integer.MAX_VALUE;

    for (int i =0; i < pointData.size(); i++) {

    FundMode currFunMode = pointData.get(i);

    float abs = Math.abs(pressX - currFunMode.floatX);

    if (abs < minXLen) {

    finalFundMode = currFunMode;

    minXLen = abs;

    }

    }

    //x

            float topY=mHeight - mPaddingBottom - mPerY *  (maxY - minY);

    int left=(int)finalFundMode.floatX-reactWidth>=mPaddingLeft?(int)finalFundMode.floatX-reactWidth:(int)finalFundMode.floatX;

    int right=(int)finalFundMode.floatX-reactWidth>=mPaddingLeft?(int)finalFundMode.floatX:reactWidth+(int)finalFundMode.floatX;

    int top=(int)finalFundMode.floatY-reactHeight>=(int)topY?(int)finalFundMode.floatY-reactHeight:(int)topY;

    int bottom=(int)finalFundMode.floatY-reactHeight>=(int)topY?(int)finalFundMode.floatY:(int)topY+reactHeight;

    Rect topDirty =new Rect(left, top, right, bottom);

    canvas.drawRect(topDirty, rectBGPaint);

    canvas.drawText(finalFundMode.time +"",

    left+(reactWidth-mLongPressPaint.measureText(finalFundMode.time+""))/2,top+reactTextMargin_top+getFontHeight(mLongPressTextSize, mLongPressPaint) /2 , mLongPressPaint);

    canvas.drawText("¥"+AppConfig.formatNum(finalFundMode.last) ,

    left+(reactWidth-mLongPressPaint.measureText(finalFundMode.time+""))/2,top+reactTextMargin_bottom+getFontHeight(mLongPressTextSize, mLongPressPaint) /2 , mLongPressPaint);

    }

    public Paint getRectBGPaint() {

    Paint paint =new Paint();

    paint.setColor(Color.BLACK);

    paint.setAntiAlias(true);

    paint.setStrokeWidth(mInnerXStrokeWidth);

    paint.setAlpha(85);

    paint.setStyle(Paint.Style.FILL);

    return paint;

    }

    //找到最大时间、最小时间和中间时间显示即可

        private void drawXPaint(Canvas canvas) {

    if(apiResult.ext.xlist.size()>=6){

    String firstX=apiResult.ext.xlist.get(0);

    String secondX=apiResult.ext.xlist.get(1);

    String thirdX=apiResult.ext.xlist.get(2);

    String forthX=apiResult.ext.xlist.get(3);

    String fifthX=apiResult.ext.xlist.get(4);

    String sixX=apiResult.ext.xlist.get(5);

    //x轴文字的高度

                halfTextWidth_X=mXYPaint.measureText(firstX)/2;

    float hight = mHeight - mPaddingBottom + xBottomTopPadding;

    canvas.drawText(firstX,

    mPaddingLeft-halfTextWidth_X,

    hight+textTopPadding, mXYPaint);

    canvas.drawText(secondX,

    mPaddingLeft + (mWidth - mPaddingLeft - mPaddingRight) /6-halfTextWidth_X,

    hight+textTopPadding, mXYPaint);

    canvas.drawText(thirdX,

    mPaddingLeft + (mWidth - mPaddingLeft - mPaddingRight) /3f-halfTextWidth_X,

    hight+textTopPadding, mXYPaint);

    canvas.drawText(forthX,

    mPaddingLeft + (mWidth - mPaddingLeft - mPaddingRight) /2-halfTextWidth_X,

    hight+textTopPadding, mXYPaint);

    canvas.drawText(fifthX,

    mPaddingLeft + (mWidth - mPaddingLeft - mPaddingRight)*2 /3-halfTextWidth_X,

    hight+textTopPadding, mXYPaint);

    canvas.drawText(sixX,

    mPaddingLeft + (mWidth - mPaddingLeft - mPaddingRight)*5/6-halfTextWidth_X,

    hight+textTopPadding, mXYPaint);

    }

    }

    private void drawYPaint(Canvas canvas) {

    //现将最小值、最大值画好

    //draw min

            if(apiResult.ext.ylist.size()>=4){

    String firstY=apiResult.ext.ylist.get(0);

    String secondY=apiResult.ext.ylist.get(1);

    String thirdY=apiResult.ext.ylist.get(2);

    String forthY=apiResult.ext.ylist.get(3);

    float txtWigth = mXYPaint.measureText(firstY) ;

    float perYWidth = (mHeight - mPaddingBottom - mPaddingTop) /3;

    canvas.drawText(AppConfig.formatNum(firstY),

    mPaddingLeft - txtWigth- yTextRightPadding,

    mHeight - mPaddingBottom, mXYPaint);

    //draw max

                canvas.drawText(AppConfig.formatNum(secondY),

    mPaddingLeft - txtWigth- yTextRightPadding,

    mPaddingTop+perYWidth*2+getFontHeight(mXYTextSize, mXYPaint) /4, mXYPaint);

    canvas.drawText(AppConfig.formatNum(thirdY),

    mPaddingLeft - txtWigth- yTextRightPadding,

    mPaddingTop+perYWidth+getFontHeight(mXYTextSize, mXYPaint) /4, mXYPaint);

    canvas.drawText(AppConfig.formatNum(forthY),

    mPaddingLeft - txtWigth- yTextRightPadding,

    mPaddingTop+getFontHeight(mXYTextSize, mXYPaint) /4, mXYPaint);

    }

    }

    private void showLongPressView() {

    mDrawLongPressPaint =true;

    invalidate();

    }

    private void hiddenLongPressView() {

    //实现蚂蚁金服延迟消失十字线

            postDelayed(new Runnable() {

    @Override

    public void run() {

    mDrawLongPressPaint =false;

    invalidate();

    }

    },200);

    }

    // 只需要把画笔颜色置为透明即可

        private void hiddenLoadingPaint() {

    mLoadingPaint.setColor(0x00000000);

    mDrawLoadingPaint =false;

    }

    private void showLoadingPaint() {

    mLoadingPaint.setColor(getColor(R.color.gray));

    mDrawLoadingPaint =true;

    }

    private int getColor(@ColorResint colorId) {

    return getResources().getColor(colorId);

    }

    private float convertDp2Px(float dpValue) {

    final float scale = getContext().getResources().getDisplayMetrics().density;

    return (dpValue * scale +0.5f);

    }

    public float getFontHeight(float fontSize, Paint paint) {

    paint.setTextSize(fontSize);

    Paint.FontMetrics fm = paint.getFontMetrics();

    return (float) (Math.ceil(fm.descent - fm.top) +2);

    }

    /**

    * 程序入口,设置数据

    */

        public void setData(ApiResult2, XYEntity> apiResult) {

    this.apiResult = apiResult;

    if (apiResult ==null || apiResult.data.size() ==0) {

    showLoadingPaint();

    invalidate();

    }else {

    if(apiResult.ext.ylist.size()>=4){

    mPaddingLeft= mXYPaint.measureText(apiResult.ext.ylist.get(3))+yTextPadding;

    maxY=Float.parseFloat(apiResult.ext.ylist.get(3));

    minY=Float.parseFloat(apiResult.ext.ylist.get(0));

    }

    mPerX = (mWidth - mPaddingLeft - mPaddingRight) / (apiResult.data.size()-1);

    mPerY = ((mHeight - mPaddingTop - mPaddingBottom) /  (maxY - minY));

    pointCount=(apiResult.data.size()-1)/24;

    //数据过来,隐藏加载更多

                hiddenLoadingPaint();

    //刷新界面

                invalidate();

    }

    }

    }

    3、总结

    自定义K_View中代码没做封装提取,理解起来相对容易。具体内容都是一些尺寸计算,看起来有点烧脑,但是静下心来慢慢咀嚼,会发现其实整个实现过程非常简单。在这基础上朋友们可以根据需求随意改动,实现更丰富多彩的效果。


    相关文章

      网友评论

          本文标题:2018-03-01

          本文链接:https://www.haomeiwen.com/subject/nzjzfftx.html