美文网首页
Android自定义柱状图控件

Android自定义柱状图控件

作者: okfindAdog | 来源:发表于2019-08-08 14:17 被阅读0次

    搜索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、动画及简单自定义控件这些基本技能的综合运用。
    希望对你有所帮助,喜欢记得点赞喔。

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

    相关文章

      网友评论

          本文标题:Android自定义柱状图控件

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