Android自定义柱状图控件

2019-08-08  本文已影响0人  okfindAdog

搜索github会发现已经有很多相对成熟的图表框架,但与我们拿到的ui效果图总会有一些差别,然后就会陷入是把别人源码下载下来修改之后作为已用,或是重新定义一个新控件的两难境地。初拿到效果图感觉还是比较复杂难以实现,但是仔细观察发现其实这就是一个列表,一个横向列表,一个可以用RecyclerView实现的列表。


效果图.gif

如果借助recyclerview的话,那么重点就在每个条目也就是柱状图每个柱子的实现。

public class BarGraphItem extends View {
    private static final String TAG = "BarGraphView";
    private Paint paint;
    private int measuredWidth;
    private int measuredHeight;
    private double ratio;
    private GradientDrawable gradientDrawable;

    public BarGraphItem(Context context) {
        super(context);
        initPaint();
    }

    public BarGraphItem(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public BarGraphItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    //设置柱子的高度
    public void setRatio(double ratio) {
        this.ratio = ratio;
        invalidate();
    }

    private void initPaint() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.colorBarBack));
        paint.setAntiAlias(true);

        int colors[] = {getResources().getColor(R.color.colorBarColor), getResources().getColor(R.color.colorGradientGreen)};
        gradientDrawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP,colors);
        //设置渐变色
        gradientDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT);
        //设置顶部圆角
        gradientDrawable.setCornerRadii(new float[]{15,15,15,15,0,0,0,0});
    }
     

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

    @Override
    protected void onDraw(Canvas canvas) {
        //画柱子的背景色
        canvas.drawRect(0, 0, measuredWidth, measuredHeight, paint);
        //计算柱子实际高度
        if (ratio != 0){
            int ratioHeight = (int) (measuredHeight * ratio + 0.5);
            //默认坐标原点在左上角,这里我们把画布移到左上角
            canvas.translate(0,measuredHeight - ratioHeight);
            //x,y,w,h
            gradientDrawable.setBounds(0,0,measuredWidth, ratioHeight);
            gradientDrawable.draw(canvas);
        }
    }
}

代码并不复杂,可以看到最终效果有很多渐变色的运用,平时我们会用shape标签绘制一些简单的图形,其中有一个gradient属性可以帮我们实现渐变效果,那在代码中可以直接使用GradientDrawable来实现同样的效果。接下来在布局文件中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center_horizontal"
    android:paddingTop="4dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <TextView
        android:textSize="9dp"
        android:text="--"
        android:id="@+id/tv_amount_bar_graph"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <RelativeLayout
        android:id="@+id/rl_bar_graph"
        android:layout_marginTop="8dp"
        android:background="@drawable/shape_bar_graph"
        android:layout_width="43dp"
        android:layout_height="wrap_content">
        <com.example.a14617.bargraphtest.view.BarGraphItem
            android:layout_centerHorizontal="true"
            android:id="@+id/bgi_bar_graph"
            android:layout_gravity="center_horizontal"
            android:layout_width="8dp"
            android:layout_height="110dp" />
    </RelativeLayout>
    <TextView
        android:textColor="#999999"
        android:textSize="12dp"
        android:gravity="center"
        android:text="--"
        android:background="#F9F9F9"
        android:layout_marginTop="8dp"
        android:id="@+id/tv_time_bar_graph"
        android:layout_width="match_parent"
        android:layout_height="40dp" />
    <ImageView
        android:id="@+id/iv_highest_bar_graph"
        android:visibility="invisible"
        android:src="@drawable/triangle_red_rose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

因为柱状图整体的渐变背景并没到底部,只是中间柱子的部分,所以把这个背景的设置放到item的布局中,也是用shape 标签实现

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:angle="90"
        android:startColor="@color/colorLightBlue"
        android:endColor="@color/colorGradientWhite"
        >
    </gradient>
</shape>

标识最高点的红色三角可以用png或其它格式的图片,为了尽量缩减包体积也可以用layerlist标签来实现,

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/shape_id">
         <!--正三角-->
        <rotate
            android:fromDegrees="45"
            android:toDegrees="45"
            android:pivotX="-40%"
            android:pivotY="80%">
            <shape android:shape="rectangle">
                <solid android:color="@color/colorAccent"/>
                <size android:width="8dp"
                    android:height="8dp"/>
            </shape>
        </rotate>
    </item>
</layer-list>

准备工作已经完成,接下来就可以愉快地进行recyclerview三步曲了。在xml布局文件中使用recyclerview(比较简单代码就省略了),然后在MainActivity中初始化recyclerview,

private void initRecyclerView() {
        //先造几条假数据
        DecimalFormat df = new DecimalFormat("0.00");
        int maxIndex = 0;
        double max = 0;
        for (int i = 1;i <= 10;i++){
            IncomeDetailsBean incomeDetailsBean = new IncomeDetailsBean();
            incomeDetailsBean.setDate(i + "点");
            double random = Math.random();
            //记录最大值
            if (random > max) {
                max = random;
                maxIndex = i - 1;
            }
            incomeDetailsBean.setIncome(df.format(random * 10) + "");
            incomeDetailsBean.setRatio(random);
            incomeDetails.add(incomeDetailsBean);
        }
        incomeDetails.get(maxIndex).setHighest(true);

      //初始化recyclerview
        layoutManager = new LinearLayoutManager(this);
       layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        barGraphAdapter = new BarGraphAdapter(this,incomeDetails);
        recyclerView.setAdapter(barGraphAdapter);
}

最后一步adapter,只看绑定控件这一部分,

@Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        //如果没有数据会有几条占位图
        if (incomeDetails == null || incomeDetails.size() == 0){
            holder.barGraphItem.setRatio(0);
            holder.tv_time.setText("--");
            holder.tv_amount.setText("--");
            if (View.VISIBLE == holder.iv_highest.getVisibility()) {
                holder.iv_highest.setVisibility(View.INVISIBLE);
                holder.tv_time.setTextSize(12);
                holder.tv_time.setTextColor(Color.parseColor("#999999"));
            }
            holder.tv_amount.setTextColor(Color.LTGRAY);
        }else {
          //绑定数据
            IncomeDetailsBean incomeDetailsBean = incomeDetails.get(position);
            holder.barGraphItem.setRatio(incomeDetailsBean.getRatio());
            holder.tv_time.setText(incomeDetailsBean.getDate());
            holder.tv_amount.setText(incomeDetailsBean.getIncome());
            holder.tv_amount.setTextColor(incomeDetailsBean.getRatio() == 0 ? Color.LTGRAY : context.getResources().getColor(R.color.colorAccent));
            holder.iv_highest.setVisibility(incomeDetailsBean.isHighest() ? View.VISIBLE : View.INVISIBLE);
            holder.tv_time.setTextSize(incomeDetailsBean.isHighest() ? 16 : 12);
            holder.tv_time.setTextColor(incomeDetailsBean.isHighest() ? context.getResources().getColor(R.color.colorAccent) : Color.parseColor("#999999"));
        }
        //缩放动画(x轴方向不变,y轴由0到1增长效果)
        Animation animation = AnimationUtils.loadAnimation(context, R.anim.scale_item);
        holder.barGraphItem.startAnimation(animation);
    }

纵向缩放动画

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="700"
    android:fromXScale="1.0"
    android:fromYScale="0.0"
    android:pivotY="100%"
    android:toXScale="1.0"
    android:toYScale="1.0">
</scale>

最后是图中用到的配色资源

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">@android:color/holo_green_dark</color>
    <color name="colorLightBlue">#3399cc00</color>
    <color name="colorBarColor">#ff669900</color>
    <color name="colorBarBack">#5099cc00</color>
    <color name="colorGradientWhite">#0599cc00</color>
    <color name="colorGradientGreen">#40669900</color>
</resources>

整个过程没有很生僻的点,基本是对Android Drawable、动画及简单自定义控件这些基本技能的综合运用。
希望对你有所帮助,喜欢记得点赞喔。

作者简介:现就职于甜橙金融信息技术部,负责安卓客户端开发工作。

上一篇 下一篇

猜你喜欢

热点阅读