Android自定義柱狀圖控件

搜索github會發(fā)現(xiàn)已經(jīng)有很多相對成熟的圖表框架飒硅,但與我們拿到的ui效果圖總會有一些差別,然后就會陷入是把別人源碼下載下來修改之后作為已用涩笤,或是重新定義一個新控件的兩難境地。初拿到效果圖感覺還是比較復雜難以實現(xiàn),但是仔細觀察發(fā)現(xiàn)其實這就是一個列表,一個橫向列表蛋辈,一個可以用RecyclerView實現(xiàn)的列表属拾。


效果圖.gif

如果借助recyclerview的話,那么重點就在每個條目也就是柱狀圖每個柱子的實現(xiàn)冷溶。

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屬性可以幫我們實現(xiàn)漸變效果苗胀,那在代碼中可以直接使用GradientDrawable來實現(xiàn)同樣的效果。接下來在布局文件中使用

<?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 標簽實現(xiàn)

<?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標簽來實現(xiàn),

<?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>

準備工作已經(jīng)完成澜驮,接下來就可以愉快地進行recyclerview三步曲了陷揪。在xml布局文件中使用recyclerview(比較簡單代碼就省略了),然后在MainActivity中初始化recyclerview,

private void initRecyclerView() {
        //先造幾條假數(shù)據(jù)
        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) {
        //如果沒有數(shù)據(jù)會有幾條占位圖
        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 {
          //綁定數(shù)據(jù)
            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飞蚓、動畫及簡單自定義控件這些基本技能的綜合運用。
希望對你有所幫助廊蜒,喜歡記得點贊喔玷坠。

作者簡介:現(xiàn)就職于甜橙金融信息技術部蜗搔,負責安卓客戶端開發(fā)工作。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末八堡,一起剝皮案震驚了整個濱河市樟凄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兄渺,老刑警劉巖缝龄,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挂谍,居然都是意外死亡叔壤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門口叙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炼绘,“玉大人,你說我怎么就攤上這事妄田“沉粒” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵疟呐,是天一觀的道長脚曾。 經(jīng)常有香客問我,道長启具,這世上最難降的妖魔是什么本讥? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鲁冯,結果婚禮上拷沸,老公的妹妹穿的比我還像新娘。我一直安慰自己薯演,他們只是感情好堵漱,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涣仿,像睡著了一般勤庐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上好港,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天愉镰,我揣著相機與錄音,去河邊找鬼钧汹。 笑死丈探,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的拔莱。 我是一名探鬼主播碗降,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼隘竭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讼渊?” 一聲冷哼從身側(cè)響起动看,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爪幻,沒想到半個月后菱皆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡挨稿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年仇轻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奶甘。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡篷店,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臭家,到底是詐尸還是另有隱情疲陕,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布侣监,位于F島的核電站,受9級特大地震影響臣淤,放射性物質(zhì)發(fā)生泄漏橄霉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一邑蒋、第九天 我趴在偏房一處隱蔽的房頂上張望姓蜂。 院中可真熱鬧,春花似錦医吊、人聲如沸钱慢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽束莫。三九已至,卻和暖如春草描,著一層夾襖步出監(jiān)牢的瞬間览绿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工穗慕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饿敲,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓逛绵,卻偏偏與公主長得像怀各,于是被迫代替她去往敵國和親倔韭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355