Android自定義控件之實現類似文件夾頂部的層層顯示的橫欄效果

目的概述

要實現的控件效果如圖(本圖來自小米3)
文件夾層層顯示的橫欄效果圖

如果說大家之前實現過的這種控件的,非常希望能和大家交流學習一下。
或者說如果大家知道類似這種效果的開源控件,也非常希望能夠告知一下。謝謝!!

思路演化

  • 先講一下我一開始的思路
    1. 一開始我的想法是定義一個LinearLayout,不斷在裏面添加TextView的控件,在onlayout()方法中進行TextView的佈局,並滑動到最尾端
    2. 滑動的話,然後通過Scroller實現滾動,當然滾動效果不是很好
    3. TextView點擊事件,因爲與滑動衝突,通過GestureDetector很好解決了這兩者的關係
  • 但是上面這種的效果不是很好,而且還涉及到了佈局邊緣位置的判斷 ,處理得也不是很好。接下來講一下我現在的實現方式,比較簡單,主要採用了HorizontalScrollView佈局實現
    1. 通過向HorizontalScrollView添加TextView,並滑動到最尾端
    2. 由於採用HorizontalScrollView,滑動與點擊事件之間的衝突已經被解決,也不用進行邊緣判斷
  • 進一步改造,實現第一個標籤不動,只是其餘標籤滑動的效果。看了一下小米對於這個佈局的實現即可明白,在最外層添加一個RelativeLayout,子佈局爲HorizontalScrollView,並添加第一個佈局標籤固定在左邊遮擋住HorizontalScrollView的第一個標籤即可

  • 最後補充一點關於標籤TextView右邊箭頭的實現,通過自定義一個繼承TextView的控件,並在onDraw()方法中畫出這樣一條路徑

代碼實現

通過上面思路的講解,下面展現實現的代碼及效果圖
  • 標籤TabView
public class TabView extends TextView {

    /**
     * 背景顏色
     */
    private int mBgColor;

    /**
     * 線段顏色
     */
    private int mLineColor;

    /**
     * 畫筆
     */
    private Paint mPaint;

    /**
     * 路徑
     */
    private Path mPath;

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);

        // 清除上次路徑
        mPaint.setColor(mBgColor);
        canvas.drawPath(mPath, mPaint);

        mPaint.setColor(mLineColor);
        mPath.reset();
        mPath.moveTo(getWidth() - 20 - getHeight() / 2, 0);
        mPath.lineTo(getWidth() - 20, getHeight() / 2);
        mPath.lineTo(getWidth() - 20 - getHeight() / 2, getHeight());
        canvas.drawPath(mPath, mPaint);

    }

    public void setTab(String tab) {
        setText(tab);
    }

    /**
     * 初始化屬性
     */
    private void init() {
        setGravity(Gravity.CENTER_VERTICAL);
        setMaxLines(1);

        mBgColor = getContext().getResources().getColor(
                R.color.TabBackGroundColor);
        mLineColor = getContext().getResources().getColor(R.color.TabLineColor);

        setBackgroundColor(mBgColor);

        mPath = new Path();
        mPaint = new Paint();
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.STROKE);

    }

    public TabView(Context context) {
        this(context, null);
    }

    public TabView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TabView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

}
  • 佈局FlowLayout
public class FlowLayout extends RelativeLayout implements View.OnClickListener {

    /**
     * Tab集合
     */
    private List<String> mTabs;

    /**
     * 存放Tab的橫向HorizontalScrollView
     */
    private HorizontalScrollView mHsv;

    /**
     * 橫向HorizontalScrollView的子佈局
     */
    private LinearLayout mHsvInnerLayout;

    /**
     * 添加一個新的標籤
     * 
     * @param text
     */
    public void addTab(String text) {

        mTabs.add(text);

        if (getHsvInnerLayoutChildCount() == 0) {
            // 爲什麼add的TextView的text沒有居中,因爲在addview後,高度爲最高,再後來addview後,高度增加
            // 添加了該tv,導致scrolleView最後面的View顯示不全
            addView(getTabView(text));
        }
        mHsvInnerLayout.addView(getTabView(text));

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        // 滑到尾端
        mHsv.smoothScrollTo(mHsvInnerLayout.getWidth(), 0);
    }

    /**
     * 獲取mHsvInnerLayout的子View數量
     * 
     * @return
     */
    private int getHsvInnerLayoutChildCount() {
        return mHsvInnerLayout.getChildCount();
    }

    /**
     * 根據tab生產View
     * 
     * @param tab
     * @return
     */
    private View getTabView(String tab) {

        View view = LayoutInflater.from(getContext()).inflate(
                R.layout.item_file_tag, this, false);

        TabView tv = (TabView) view.findViewById(R.id.item_tag);
        tv.setTab(tab);
        tv.setTag(tab); // 用於點擊事件的判斷
        ((TextView) tv).setTextColor(getTabColorStateList());
        tv.setOnClickListener(this);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);

        view.setLayoutParams(params);

        return view;
    }

    private void init() {

        mTabs = new ArrayList<String>();

        // 設置子view垂直方向上居中
        setGravity(Gravity.CENTER_HORIZONTAL);
        setBackgroundColor(getResources().getColor(R.color.TabBackGroundColor));

        initHSV();

    }

    /**
     * 初始化HorizontalScrollView
     */
    private void initHSV() {
        mHsv = new HorizontalScrollView(getContext());
        // 隱藏滾動條
        mHsv.setHorizontalScrollBarEnabled(false);

        LayoutParams mHsvParams = new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
        mHsvParams.rightMargin = 50;

        mHsvInnerLayout = new LinearLayout(getContext());
        LayoutParams mHsvInnerLayoutParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        mHsvInnerLayout.setGravity(Gravity.CENTER_VERTICAL);
        // 添加子View
        mHsv.addView(mHsvInnerLayout, mHsvInnerLayoutParams);

        addView(mHsv, mHsvParams);

    }

    public FlowLayout(Context context) {
        this(context, null);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 獲取ColorStateList對象
     * 
     * @return
     */
    private ColorStateList getTabColorStateList() {
        ColorStateList csl = new ColorStateList(new int[][] {
                { android.R.attr.state_pressed },
                { -android.R.attr.state_pressed } }, new int[] { Color.BLACK,
                Color.GRAY });

        return csl;
    }

    private onTabClickListener mOnTabClickListener;

    /**
     * tab點擊事件接口
     */
    public interface onTabClickListener {
        void onTabClick(String tab);
    }

    public void setOnTabClickListener(onTabClickListener l) {
        this.mOnTabClickListener = l;
    }

    @Override
    public void onClick(View v) {

        String tab = (String) v.getTag();
        if (tab == null)
            return;

        if (mOnTabClickListener != null) {
            mOnTabClickListener.onTabClick(tab);
        }

        // 移除後面Tab
        int index = mTabs.indexOf(tab);
        int size = mTabs.size();

        // 不作處理
        if (index == -1 || index == size - 1) {
            return;
        }

        for (int i = size - 1; i > index; i--) {
            mHsvInnerLayout.removeViewAt(i);
            mTabs.remove(i);
        }

    }
}

效果

最終效果

總結

  • 總體來說沒什麼難度,但是還是值得學習記錄一下過程思想

最後

  • 最近可能開始要忙了,不過最近在學習Android開源框架Volley,所以接下來將會對記錄一下我對Volley的理解。對Volley的用法,源碼,設計模式,HTTP,自定義異常等方面做個記錄

  • 最後呢,如果你有其他思路或者開源控件,非常期待你的分享,謝謝

  • 如果本篇文章有哪裏錯誤不足之處,或者哪方面可以做更好的封裝,也希望能夠指出,一起探討學習。
發佈了24 篇原創文章 · 獲贊 5 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章