Android自定义控件之实现类似文件夹顶部的层层显示的横栏效果

原创 2015年11月17日 18:03:21

目的概述

要实现的控件效果如图(本图来自小米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,自定义异常等方面做个记录

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

  • 如果本篇文章有哪里错误不足之处,或者哪方面可以做更好的封装,也希望能够指出,一起探讨学习。
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

android自定义控件SwipeLayout实现类似QQ滑动删除效果2

之间就说过还有一个实现方式,一时忙没写。 今天我这里就写下吧。 这个是写了继承LinearLayout的控件。 先说下思路吧,这里我写了个自定义控件SwipeLayout继承了LinearLay...

android自定义控件SlidingButtonView实现类似QQ滑动删除效果

最近遇到这个需求,下面给大家推荐两办法都是自定义控件一个是继承了HorizontalScrollView的另一个是继承了LinearLayout 那么先说下第一个,首先实现滑动出现删除菜单就一定会需...

Android 刮刮卡效果 自定义控件实现

  • 2017年11月17日 10:57
  • 4.67MB
  • 下载

Android自定义控件之实现listview滑动时渐隐渐现顶部栏

我在开发的时候遇到了这样的需求,就是在listview的滑动中,需要对顶部的栏目由透明慢慢的变为不透明的状态,就是以下的效果 最先开始的时候想的很简单,无非就是监听listview的滑动距离...

Android 刮刮卡效果 自定义控件实现

  • 2014年10月18日 18:44
  • 4.66MB
  • 下载

自定义实现类似android主界面的滑屏换屏控件

直接效果图 ,更多内容可以参看:我的android阅读软件“微读”-做最简单的手机阅读软件             实现思路,刚开始的时候我是用ViewFlipper控件来做非常的简单但是实现不了...
  • artwebs
  • artwebs
  • 2013年03月05日 14:48
  • 777
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android自定义控件之实现类似文件夹顶部的层层显示的横栏效果
举报原因:
原因补充:

(最多只允许输入30个字)