搜索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来实现同样的效果。接下来在布局文件中使用
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">
android:textSize="9dp"
android:text="--"
android:id="@+id/tv_amount_bar_graph"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
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">
android:layout_centerHorizontal="true"
android:id="@+id/bgi_bar_graph"
android:layout_gravity="center_horizontal"
android:layout_width="8dp"
android:layout_height="110dp" />
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" />
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" />
因为柱状图整体的渐变背景并没到底部,只是中间柱子的部分,所以把这个背景的设置放到item的布局中,也是用shape 标签实现
android:shape="rectangle">
android:angle="90"
android:startColor="@color/colorLightBlue"
android:endColor="@color/colorGradientWhite"
>
标识最高点的红色三角可以用png或其它格式的图片,为了尽量缩减包体积也可以用layerlist标签来实现,
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="80%">
android:height="8dp"/>
准备工作已经完成,接下来就可以愉快地进行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);
}
纵向缩放动画
android:duration="700"
android:fromXScale="1.0"
android:fromYScale="0.0"
android:pivotY="100%"
android:toXScale="1.0"
android:toYScale="1.0">
最后是图中用到的配色资源
#3F51B5
#303F9F
@android:color/holo_green_dark
#3399cc00
#ff669900
#5099cc00
#0599cc00
#40669900
整个过程没有很生僻的点,基本是对Android Drawable、动画及简单自定义控件这些基本技能的综合运用。
希望对你有所帮助,喜欢记得点赞喔。
作者简介:现就职于甜橙金融信息技术部,负责安卓客户端开发工作。