9

实现一个可定制化的FlowLayout

 4 years ago
source link: https://juejin.im/post/5e2f9dc7e51d4558836e3f99
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

实现一个可定制化的FlowLayout

FlowLayout 继承于 ViewGroup ,可以快速帮您实现 Tablayout 以及 Label 标签,内含多种效果,帮您快速实现 APP UI 功能,让您专注代码架构,告别繁琐UI。

如果你也想自己写一个,可以参考以下几篇文章

实现一个可定制化的TabFlowLayout(一) -- 测量与布局

实现一个可定制化的TabFlowLayout(二) -- 实现滚动和平滑过渡

实现一个可定制化的TabFlowLayout(三) -- 动态数据添加与常用接口封装

实现一个可定制化的TabFlowLayout(四) -- 与ViewPager 结合,实现炫酷效果

实现一个可定制化的TabFlowLayout -- 原理篇

实现一个可定制化的TabFlowLayout -- 说明文档

FlowLayout 和 Recyclerview 实现双联表联动

如果您也想快速实现banner,可以使用这个库 github.com/LillteZheng…

allprojects {
    repositories {
       ...
        maven { url 'https://jitpack.io' }
        
    }
}
复制代码

最新版本请以工程为准:代码工程:实现一个可定制化的FlowLayout

implementation 'com.github.LillteZheng:FlowHelper:v1.17'
复制代码

首先,就是 TabFlowLayout 的效果,它的布局支持横竖两种方式,首先先看支持的效果:

没有结合ViewPager 结合ViewPager
16fea010397b7745?imageslim
16fea0103f3253e7?imageslim
TabFlowLayout竖直,RecyclerView联动效果
16fea07a049ba813?imageslim

除了 TabFlowLayout,还有 LAbelFlowLayout 标签式布局,支持自动换行与显示更多

LabelFlowLayout LabelFlowLayout 显示更多
17000890cba2bdc0?imageslim
17000890cb9f602d?imageslim

可以看到 目前 TabFlowLayout 支持以下效果:

  • 矩形
  • 三角形
  • 圆角
  • shape 或者 bitmap 等资源文件
  • 自定义功能
  • 放大Item效果,与上述效果可共用
  • 颜色渐变效果,需要使用 TabColorTextView 控件,与上述效果可共用,只支持有viewpager 的情况
  • 竖直效果,需要设置 tab_orientation = vertical

主要是 TabFlowLayout 和 LabelFlowLayout 这两个控件

3.1 TabFlowLayout

首先是在 xml 中,填写 TabFlowLayout 控件,它支持横竖排列,默认横向,可以使用tab_orientation = "vertical" 更换成竖直排列,一个不带效果,支持横向的 TabFlowLayout 如下:

XML

<com.zhengsr.tablib.view.flow.TabFlowLayout
    android:id="@+id/resflow"
    android:layout_width="wrap_content"
    android:layout_marginTop="5dp"
    android:background="#6D8FB0"
    android:layout_height="wrap_content"/>
复制代码

比如要加矩形,三角形,可以使用 app:tab_type 这个属性,比如一个矩形:

<com.zhengsr.tablib.view.flow.TabFlowLayout
    android:id="@+id/rectflow"
    android:layout_width="wrap_content"
    android:layout_marginTop="5dp"
    app:tab_type="rect"
    app:tab_color="@color/colorPrimary"
    app:tab_height="3dp"
    app:tab_width="20dp"
    app:tab_margin_b="3dp"
    android:background="@color/black_ff_bg"
    app:tab_scale_factor="1.2"
    app:tab_item_autoScale="true"
    android:layout_height="wrap_content"/>
复制代码

这里解释一下关键几个自定参数

  • tab_type : 填写效果类型,这里使用 rect
  • tab_color : tab 的颜色
  • tab_width :tab 的宽度,不填的话,默认控件宽度
  • tab_height : tab 的 高度
  • tab_margin_b :margin_bottom 的意思,相应的还有 l,t,r,b
  • tab_item_autoScale : 是否自动放大缩小效果,默认false
  • tab_scale_factor :放大因子,这里放大1.2 倍,默认为 1

上面几个为基础属性,这里填写 tri 也可以同样设置。当 type 为 round 或者 res 时,width 和 height 则可以不填,因为要根据控件本身去适配大小。

其他说明,可以参看下面的自定义属性说明。

Java

那么,在 xml 写好了,接着,就是在 Activity 中,这样写:

private void rectFlow(){
    TabFlowLayout flowLayout = findViewById(R.id.rectflow);
    //设置数据,这里以 setAdapter 的形式
    flowLayout.setAdapter(new TabFlowAdapter<String>(R.layout.item_msg,mTitle) {
        @Override
        public void onItemSelectState(View view, boolean isSelected) {
            super.onItemSelectState(view, isSelected);
            //选中时,可以改变不同颜色,如果你的background 为 selector,可以不写这个
            if (isSelected){
                setTextColor(view,R.id.item_text,Color.WHITE);
            }else{
                setTextColor(view,R.id.item_text,getResources().getColor(R.color.unselect));
            }
        }

        @Override
        public void bindView(View view, String data, int position) {
            /**
             * 绑定数据,可以使用 setText(..) 等快捷方式,也可以视同 view.findViewById()
             * 同时,当你的子控件需要点击事件时,可以通过  addChildrenClick() 注册事件,
             * 然后重写 onItemChildClick(..) 即可拿到事件,否则就自己写。
             * 自己的点击和长按不需要注册
             */
            setText(view,R.id.item_text,data)
                    .setTextColor(view,R.id.item_text,getResources().getColor(R.color.unselect));
            if (position == 0){
                setVisible(view,R.id.item_msg,true);
            }
            
            // 注册子控件的点击事件
            //addChildrenClick(view,R.id.item_text,position);
            //注册子控件的长按事件
            //addChildrenLongClick(view,R.id.item_text,position);

        }
    });

}
复制代码

可以看到,只需要设置 adapter 就行了,需要注意的是你要传入子控件的 layout,这样方便你自定义你的布局,比如一个TextView 和一个红点点。具体细节,可以参看这个:

实现一个可定制化的TabFlowLayout(三) -- 动态数据添加与常用接口封装

如果你需要使用颜色渐变的效果,TextView 换成 TabColorTextView 就可以了,比如:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <com.zhengsr.tablib.view.TabColorTextView
        android:id="@+id/item_text"
        android:layout_width="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        tools:text="测试"
        android:paddingTop="6dp"
        android:paddingBottom="6dp"
        android:paddingStart="12dp"
        android:paddingEnd="12dp"
        android:textSize="14sp"
        app:colortext_default_color="@color/unselect"
        app:colortext_change_color="@color/colorAccent"
        android:gravity="center"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/item_msg"
        android:layout_width="5dp"
        android:layout_height="5dp"
        android:gravity="center"
        android:textSize="8dp"
        android:background="@drawable/shape_red_radius"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="5dp"
        android:visibility="gone"
       />



</android.support.constraint.ConstraintLayout>
复制代码

3.1.1、结合Viewpager

结合 ViewPager 非常简单,如下:

flowLayout.setViewPager(...) 即可.

复制代码

它有几个方法,参考这个解释就可以了。

  /**
   * 配置viewpager
   * @param viewPager
   * @param textId  view 中 textview 的id,用于TextView的颜色变化
   * @param selectedIndex 默认选中的item,初始值为0,也可以从第二页或者其他 位置
   * @param unselectedColor 没有选中的颜色,如果为 TabColorTextView 不需要些这个
   * @param selectedColor 选中的颜色,如果为 TabColorTextView 不需要些这个
   */
  public void setViewPager(
                        ViewPager viewPager,
                        int textId, 
                        int selectedIndex, 
                        int unselectedColor, 
                        int selectedColor) {}
复制代码

为了避免卡顿,当viewpager结合fragment时,可以有以下优化手段:

  • fragment 布局复杂或者网络加载数据时,建议在懒加载中去初始化或者加载数据
  • viewpager 增加缓存,setOffscreenPageLimit(3)。
  • setCurrentItem(position,false),滚动设置为false,然后用 flowLayout 实现item的动画,flowLayout.setItemAnim(position)

如果您觉得viewpager切换太快,可以使用 ViewPagerHelperUtils.initSwitchTime(getContext(), viewPager, 600) 改变滚动速度

3.1.2、自定义属性动态配置

可能你不想在 xml 直接定死,那么可以直接使用 TabBean 去配置,比如使用 tab_type=res, 你的drawable 使用 shape 当移动背景的配置如下:

private void resFlow(){
    final TabFlowLayout flowLayout = findViewById(R.id.resflow);

    flowLayout.setViewPager(mViewPager);
    /**
     * 配置自定义属性
     */

    TabBean bean = new TabBean();
    bean.tabType = FlowConstants.RES;
    bean.tabItemRes = R.drawable.shape_round;
    //点击的动画执行时间 ms
    bean.tabClickAnimTime = 300;
    bean.tabMarginLeft = 5;
    bean.tabMarginTop = 12;
    bean.tabMarginRight = 5;
    bean.tabMarginBottom = 10;
    
    //动态设置自定义属性
    flowLayout.setTabBean(bean);

    flowLayout.setAdapter(new TabFlowAdapter<String>(R.layout.item_msg,mTitle) {
        @Override
        public void bindView(View view, String data, int position) {
            setText(view,R.id.item_text,data);
        }

        @Override
        public void onItemClick(View view, String data, int position) {
            super.onItemClick(view, data, position);
            mViewPager.setCurrentItem(position);
        }
    });
}
复制代码

3.1.3、自定义action

如果上面没有你想要的怎么办?,那么项目也支持用户自定义,比如自定义一个白色圆点效果,只需要继承 BaseAction 即可,上面效果图中的 圆点,其实是继承 BaseAction 后写的,代码如下:


    /**
     * 绘制一个圆的指示器
     * 除了继承 BaseAction,还需要些 TabRect
     */
    class CircleAction extends BaseAction{
        @Override
        public void config(TabFlowLayout parentView) {
            super.config(parentView);
            View child = parentView.getChildAt(0);
            if (child != null) {
                float l = parentView.getPaddingLeft() + child.getMeasuredWidth()/2;
                float t = parentView.getPaddingTop() +  child.getMeasuredHeight() - mTabHeight/2 -mMarginBottom;
                float r = mTabWidth + l;
                float b = child.getMeasuredHeight() - mMarginBottom;
                //先设置 TabRect 的范围,即一个 view 的左边,方便后面的移动
                mTabRect.set(l,t,r,b);
            }
        }


        @Override
        protected void valueChange(TabValue value) {
            super.valueChange(value);
            
            /**
             * value 子控件在滚动时的 left 和 right,可以理解为偏移量
             * TabRect 为控件移动时的局域。
             */
            //由于自定义的,都是从left 开始算起的,所以这里还需要加上圆的半径
            mTabRect.left = value.left + mTabWidth/2;
        }

        @Override
        public void draw(Canvas canvas) {
            canvas.drawCircle(mRect.left,mRect.top,mTabWidth/2,mPaint);
        }
    }
复制代码

通过重写 valueChange 拿到移动偏移量,然后通过 flowLayout.setAction(new CircleAction()) 即可。如果是竖直方向,拿到 value.top,value.bottom 再去写逻辑即可。

3.1.4、参考代码

上面的效果,可以参考以下代码:

有ViewPager的布局代码

有ViewPager的Activity

3.2、TabFlowLayout 竖直效果

前面说到,只需要把 tab_orientation 设置成 vertical 即可,相应的 当 type 为 rect 或者 tri 时,还可以通过 tab_tab_action_orientaion 选择 left 还是right 的效果:

    <com.zhengsr.tablib.view.flow.TabFlowLayout
        android:id="@+id/tabflow"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:tab_type="rect"
        app:tab_color="@color/colorPrimary"
        app:tab_orientation="vertical"
        app:tab_width="2dp"
        app:tab_height="20dp"
        app:tab_action_orientaion="left"
        android:background="@color/page_gray_cccc"
        />
复制代码

效果如下:

1

和 recyclerview 的联动效果,可以参考在这个:

Recyclerview 实现双联表联动

3.3 LabelFlowLayout

LabelFlowLayout 竖向布局,支持自动换行,单选、多选、长按等功能.

它的状态变化,根据 view 的 selected 来,所以大家可以写 selector 当背景,或者在方法中自己设置

3.3.1 使用

LabelFlowLayout 默认单选,在 xml 这样配置:

<com.zhengsr.tablib.view.flow.LabelFlowLayout
    android:id="@+id/singleflow"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
/>
复制代码

FlowLayout 使用也使用 adapter 去配置数据:

LabelFlowLayout flowLayout = findViewById(R.id.singleflow);
final LabelFlowAdapter adapter;
flowLayout.setAdapter(adapter = new LabelFlowAdapter<String>(R.layout.item_textview,mTitle){
    /**
     * 绑定数据,可以使用 setText(..) 等快捷方式,也可以视同 view.findViewById()
     * 同时,当你的子控件需要点击事件时,可以通过  addChildrenClick() 注册事件,
     * 然后重写 onItemChildClick(..) 即可拿到事件,否则就自己写。
     * 自己的点击和长按不需要注册
     */
    @Override
    public void bindView(View view, String data, int position) {
        setText(view,R.id.item_text,data);
        // 注册子控件的点击事件
        //addChildrenClick(view,R.id.item_text,position);
    }
    @Override
    public void onItemSelectState(View view, boolean isSelected) {
        super.onItemSelectState(view, isSelected);
        TextView textView = view.findViewById(R.id.item_text);
        if (isSelected) {
            textView.setTextColor(Color.WHITE);
        } else {
            textView.setTextColor(Color.GRAY);
        }
    }
});
复制代码

3.3.2 多选

其实只需要配置 flowLayout.setMaxSelectCount(3); 就可以了,然后adapter 中重写:

@Override
public void onReachMacCount(List<Integer> ids, int count) {
    super.onReachMacCount(ids, count);
    Toast.makeText(LabelActivity.this, "最多只能选中 "+count+" 个"+" 已选中坐标: "+ids, Toast.LENGTH_SHORT).show();
}
复制代码

如果您选默认选中其中一些item,可以使用 flowLayout.setSelects(2,3,5);

3.3.3 长按

其实就是长按view,至于状态的变化,由自己去写:

    private void canLongFlow(){
        LabelFlowLayout flowLayout = findViewById(R.id.longflow);
        flowLayout.setAdapter(new LabelFlowAdapter<String>(R.layout.item_search_layout,mTitle2) {
            @Override
            public void bindView(View view, String data, int position) {
                setText(view,R.id.search_msg_tv,data)
                        .addChildrenClick(view,R.id.search_delete_iv,position);
            }

            @Override
            public void onItemSelectState(View view, boolean isSelected) {
                super.onItemSelectState(view, isSelected);
                if (!isSelected){
                    view.setBackgroundResource(R.drawable.shape_search);
                    setVisible(view,R.id.search_delete_iv,false);
                }
            }

            @Override
            public void onItemClick(View view, String data, int position) {
                super.onItemClick(view, data, position);
                Toast.makeText(LabelActivity.this, "点击了: "+data, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemChildClick(View childView, int position) {
                super.onItemChildClick(childView, position);
                if (childView.getId() == R.id.search_delete_iv){
                    mTitle2.remove(position);
                    notifyDataChanged();
                }
            }

            @Override
            public boolean onItemLongClick(View view,int position) {
                /**
                 * 置所有view 的 select 为 false
                 */
                resetStatus();
                view.setBackgroundResource(R.drawable.shape_search_select);
                setVisible(view,R.id.search_delete_iv,true);
                return super.onItemLongClick(view,position);
            }


        });
    }

复制代码

3.3.5 显示更多

LabelFlowLayout 还支持显示更多的功能,如图:

1

配置的 xml 如下:

<com.zhengsr.tablib.view.flow.LabelFlowLayout
    android:id="@+id/labelflow"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="10dp"
    android:layout_marginStart="10dp"
    app:label_showLine="2"
    app:label_showMore_layoutId="@layout/show_more"
    app:label_showMore_Color="@color/white"/>
复制代码
  • label_showLine 表示最多显示的行数
  • label_showMore_layoutId 表示显示更多的layoutId,这样方便客制化
  • label_showMore_Color 表示主背景色,用来设置 shader 虚化

上面的 label_showMore_Color 可能不太理解,其实就是把 显示更多的 LayoutId 转成bitmap,显示在下面;虚化怎么办呢?

其实就是给 paint 设置一个 shader,上面为透明色,下面给背景色一样,就能达到虚化的效果。如:

 /**
 * 同时加上一个 shader,让它有模糊效果
 */
Shader shader = new LinearGradient(0, 0, 0,
        getHeight(), Color.TRANSPARENT, mShowMoreColor, Shader.TileMode.CLAMP);
mPaint.setShader(shader);
复制代码

3.3.6 配置自定义属性

当然,也支持动态配置自定义属性。如下:

LabelBean bean = new LabelBean();
bean.showLines = 2;
bean.showMoreLayoutId = R.layout.show_more;
bean.showMoreColor = Color.WHITE;
flowLayout.setLabelBean(bean);
复制代码

3.3.7参考代码:

布局代码

Activity代码

四、Adpater 支持的方法

TabFlowLayout 和 LAbelFlowLayout 都是通过 setAdapter 的方式去加载数据的,除了支持 setText(..) ,setTextColor(..) ,setImageView 等,还支持 click 事件,具体的方法,可以参考下面的代码:

Adapter方法

五、自定义属性列表

TabFlowLayout

名称 类型 说明
tab_type rect,tri,round,color,res tab的类型,目前支持矩形,三角形、圆角、颜色渐变、资源res
tab_color color 指示器的颜色,当类型为 rect、tri、roud是可以通过它定义
tab_width dimension 指示器的宽度,如果不写,则根据控件自身大小
tab_height dimension 指示器高度
tab_item_res reference 指示器的背景,比如shape,bitmap等,只对 res 起作用
tab_round_size dimension 圆角的大小,只对round起作用
tab_margin_l dimension 左偏移
tab_margin_t dimension 上偏移
tab_margin_r dimension 右偏移
tab_margin_b dimension 下偏移
tab_click_animTime integer 点击动画的时间,默认300ms
tab_item_autoScale boolean 开启放大缩小的效果
tab_scale_factor float 放大倍数
tab_orientation integer vertical竖直防线,horizontal横向,默认横向
tab_action_orientaion integer left坐标,right右边,只支持 tri、rect 两种效果
tab_isAutoScroll boolean 是否支持自动滚动,默认为true

TabColorTextView

名称 类型 说明
colortext_default_color color 默认颜色
colortext_change_color color 需要渐变颜色

LabelFlowLayout

名称 类型 说明
label_maxcount integer 最大选择个数
label_iaAutoScroll boolean 是否支持自动滚动
label_showLine integer 最多显示的行数
label_showMore_layoutId integer 显示更多的layoutId
label_showMore_Color color 显示更多的背景色,为了虚化作用

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK