乐趣区

Android条形统计图实现

StripeStaticsView.java

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.careeach.sport.R;
import java.util.List;

/**
 * 条形统计图
 */
public class StripeStaticsView extends View {
    private List<Integer> values;
    private List<String> titles;

    private int height;
    private int width;

    private Paint stripePaint;

    private Paint stripeSelectedPaint; // 选中状态
    private Paint titleTextPaint;       //  题目字
    private Paint bodyBackGroundPaint;
    private Paint titleBackGroundPaint;

    private float itemHeight;             // 一个值对于高度
    //ATTR
    private int itemColor;          // 条形色彩
    private float itemWidth;        // 条形宽度
    private float itemSpaceWidth;  // 条形间隙

    private int itemSelectColor;
    private int maxValue;

    private float centerX;
    private float startX; // 第一条条形起始 X 座标
    private float moveWidth = 0f;
    private float touchStarX = 0;
    private float titleHeight;          // 题目高度
    private float titleTextY;          // 题目 Y 坐标
    private float titleTextSize;        // 题目字体大小
    private float titleTextLineSpace;  // 题目行间隙
    private int titleTextColor;         // 题目字色彩
    private int titleTextSelectColor;         // 题目字色彩

    private int bodyBackGroundColor;         // 主背景
    private int titleBackGroundColor;         // 题目背景

    private boolean isMove = false;
    private boolean isUp = false;

    private OnStripeStaticsListener onStripeStaticsListener;

    private int selectPosition = -1;

    private TouchEventCountThread touchEventCountThread;

    private final int HANDLER_WHAT_CLICK = 1;
    private final int HANDLER_WHAT_SELECT_CHANGE = 2;

    private ViewGroup parentViewGroup;


    public StripeStaticsView(Context context) {super(context);
    }

    public StripeStaticsView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);
        init(context, attrs);
    }

    public StripeStaticsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
        init(context, attrs);

    }

    public StripeStaticsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StripeStaticsView);
        itemColor = typedArray.getColor(R.styleable.StripeStaticsView_itemColor, Color.BLACK);
        itemSelectColor = typedArray.getColor(R.styleable.StripeStaticsView_itemSelectColor, Color.WHITE);
        itemSpaceWidth = typedArray.getDimension(R.styleable.StripeStaticsView_itemSpaceWidth, Color.BLACK);
        itemWidth = typedArray.getDimension(R.styleable.StripeStaticsView_itemWidth, dip2px(20));
        maxValue = typedArray.getInt(R.styleable.StripeStaticsView_maxValue, 100);
        titleTextSize = typedArray.getDimension(R.styleable.StripeStaticsView_titleTextSize, sp2px(12));
        titleTextLineSpace = typedArray.getDimension(R.styleable.StripeStaticsView_titleTextLineSpace, dip2px(10));
        titleTextColor = typedArray.getColor(R.styleable.StripeStaticsView_titleTextColor, Color.WHITE);
        titleTextSelectColor = typedArray.getColor(R.styleable.StripeStaticsView_titleTextSelectColor, Color.WHITE);
        bodyBackGroundColor = typedArray.getColor(R.styleable.StripeStaticsView_contentBGColor, Color.WHITE);
        titleBackGroundColor = typedArray.getColor(R.styleable.StripeStaticsView_titleBGColor, Color.WHITE);
        typedArray.recycle();
        stripePaint = new Paint();
        stripePaint.setColor(itemColor);
        stripePaint.setAntiAlias(true);
        stripePaint.setStyle(Paint.Style.FILL);

        stripeSelectedPaint = new Paint();
        stripeSelectedPaint.setColor(itemSelectColor);
        stripeSelectedPaint.setAntiAlias(true);
        stripeSelectedPaint.setStyle(Paint.Style.FILL);

        titleTextPaint = new Paint();
        titleTextPaint.setColor(titleTextColor);
        titleTextPaint.setTextSize(titleTextSize);
        titleTextPaint.setAntiAlias(true);

        bodyBackGroundPaint = new Paint();
        bodyBackGroundPaint.setColor(bodyBackGroundColor);
        bodyBackGroundPaint.setStyle(Paint.Style.FILL);

        titleBackGroundPaint = new Paint();
        titleBackGroundPaint.setColor(titleBackGroundColor);
        titleBackGroundPaint.setStyle(Paint.Style.FILL);
        touchEventCountThread = new TouchEventCountThread();}

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();}

    @Override
    protected void onDraw(Canvas canvas) {super.onDraw(canvas);
        centerX = (width - itemWidth) / 2;
        if (values != null && values.size() > 0) {startX = ((width / 2) + (itemWidth / 2)) - ((itemWidth * values.size()) + ((values.size() - 1) * itemSpaceWidth));
        }
        titleHeight = titleTextSize + (titleTextLineSpace * 2);
        itemHeight = (height - titleHeight) / maxValue;
        titleTextY = height - titleTextSize;

        // 主背景
        canvas.drawRect(0f, 0, width, height - titleHeight, bodyBackGroundPaint);

        // 题目背景
        canvas.drawRect(0f, height - titleHeight, width, height, titleBackGroundPaint);

        if (values != null && values.size() > 0 && titles != null && titles.size() == values.size()) {for (int i = 0; i < values.size(); i++) {int value = values.get(i);
                float left = startX + (itemWidth + itemSpaceWidth) * i + moveWidth;
                float top = height - titleHeight - (itemHeight * value);
                float right = left + itemWidth;
                float bottom = height - titleHeight;
                String title = titles.get(i);
                float textWidth = titleTextPaint.measureText(title, 0, title.length());
                float textX = left + ((itemWidth - textWidth) / 2);
                // 判断是否核心
                if (left >= centerX && left <= (width + itemWidth) / 2) {canvas.drawRect(left, top, right, bottom, stripeSelectedPaint);
                    // 题目
                    titleTextPaint.setColor(titleTextSelectColor);
                    canvas.drawText(title, textX, titleTextY, titleTextPaint);
                    if (i == 0 || selectPosition != i) {Message message = handler.obtainMessage();
                        message.what = HANDLER_WHAT_SELECT_CHANGE;
                        message.arg1 = i;
                        message.sendToTarget();}
                    selectPosition = i;
                } else {canvas.drawRect(left, top, right, bottom, stripePaint);
                    // 题目
                    titleTextPaint.setColor(titleTextColor);
                    canvas.drawText(title, textX, titleTextY, titleTextPaint);
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchStarX = event.getX();
                isMove = false;
                isUp = false;
                postDelayed(touchEventCountThread, 100);
                if(parentViewGroup != null){parentViewGroup.requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                isMove = true;
                if (Math.abs(event.getX() - touchStarX) > 1) {moveWidth += event.getX() - touchStarX;
                    postInvalidate();
                    touchStarX = event.getX();}
                break;
            case MotionEvent.ACTION_UP:
                isUp = true;
                if (isMove) {aligning();
                }
                if(parentViewGroup != null){parentViewGroup.requestDisallowInterceptTouchEvent(false);
                }
                break;
        }
        //return super.onTouchEvent(event);
        return true;
    }

    /**
     * 对齐,让靠两头的条形移至正中间
     */
    private void aligning() {if(values == null || values.isEmpty()){return;}
        Float tagX = null;
        Float tagXValue = null;
        for (int i = 0; i < values.size(); i++) {float left = startX + (itemWidth + itemSpaceWidth) * i + moveWidth;
            float distance = Math.abs(centerX - left);
            if (tagX == null || distance < tagX) {
                tagX = distance;
                tagXValue = centerX - left;
            }
        }
        if (tagX != null) {moveWidth += tagXValue;}
        isMove = false;
        postInvalidate();}

    public void setValue(List<Integer> values) {
        this.values = values;
        selectPosition = 0;
    }

    public void setTitle(List<String> titles) {this.titles = titles;}

    public void refresh() {postInvalidate();
    }

    private int dip2px(float dpValue) {final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    private float sp2px(float spValue) {final float scale = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (spValue * scale + 0.5f);
    }

    class TouchEventCountThread implements Runnable {
        @Override
        public void run() {if (!isMove && isUp && values != null && values.size() > 0 && titles != null && titles.size() == values.size()) {for (int i = 0; i < values.size(); i++) {float left = startX + (itemWidth + itemSpaceWidth) * i + moveWidth;
                    float right = left + itemWidth;
                    if (touchStarX >= left && right >= touchStarX) {selectTo(i);
                        if (onStripeStaticsListener != null) {Message message = handler.obtainMessage();
                            message.what = HANDLER_WHAT_CLICK;
                            message.arg1 = i;
                            message.sendToTarget();}
                        break;
                    }
                }
            }
        }
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {super.handleMessage(msg);
            switch (msg.what) {
                case HANDLER_WHAT_CLICK:
                    if(onStripeStaticsListener != null) {onStripeStaticsListener.onItemClick(msg.arg1);
                    }
                    break;
                case HANDLER_WHAT_SELECT_CHANGE:
                    if(onStripeStaticsListener != null) {onStripeStaticsListener.onSelectChanged(msg.arg1);
                    }
                    break;
            }

        }
    };


    public void selectTo(int position) {if (selectPosition == position) {return;}
        if (position > selectPosition) {moveWidth -= ((itemWidth + itemSpaceWidth) * (position - selectPosition));
        } else {moveWidth += ((itemWidth + itemSpaceWidth) * (selectPosition - position));
        }
        postInvalidate();}

    public interface OnStripeStaticsListener {void onItemClick(int position);

        void onSelectChanged(int position);
    }

    public void setOnStripeStaticsListener(OnStripeStaticsListener onClickListener) {this.onStripeStaticsListener = onClickListener;}

    public void setParentViewGroup(ViewGroup parentViewGroup) {this.parentViewGroup = parentViewGroup;}
}

attr.xml 增加属性

<declare-styleable name="StripeStaticsView">
    <attr name="itemWidth" format="dimension" />
    <attr name="itemColor" format="color" />
    <attr name="itemSelectColor" format="color" />
    <attr name="itemSpaceWidth" format="dimension" />
    <attr name="maxValue" format="integer" />
    <attr name="titleTextSize" format="dimension" />
    <attr name="titleTextLineSpace" format="dimension" />
    <attr name="titleTextColor" format="color" />
    <attr name="titleTextSelectColor" format="color" />
    <attr name="contentBGColor" format="color" />
    <attr name="titleBGColor" format="color" />
</declare-styleable>

应用

<xxx.StripeStaticsView
    android:id="@+id/stripeStaticsView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginTop="@dimen/statics_stripe_view_margin_top"
    app:contentBGColor="#7dded4"
    app:itemColor="#80ffffff"
    app:itemSelectColor="@android:color/white"
    app:itemSpaceWidth="5dp"
    app:itemWidth="30dp"
    app:maxValue="100"
    app:titleBGColor="@android:color/white"
    app:titleTextColor="@color/txt_describe"
    app:titleTextLineSpace="8dp"
    app:titleTextSelectColor="@color/colorPrimary"
    app:titleTextSize="10sp" />
// 设置值
void setValue(List<Integer> values)

// 设置值对应的题目(注:数量要对应)void setTitle(List<String> titles)

// 设置题目和内容后刷新控件
void refresh()

// 控件增加在高低滑动控件中时,当手在该控件左右滑动时不会触发高低滑动,添强体验,如 ListView
void setParentViewGroup(ViewGroup parentViewGroup)
退出移动版