美文网首页UI效果仿写程序员的职业规划
仿淘宝/漫画岛底部栏实现--解决TabLayout clipch

仿淘宝/漫画岛底部栏实现--解决TabLayout clipch

作者: SLTPAYA | 来源:发表于2017-02-09 15:52 被阅读3480次

    最近在写一个需求,要求点击底部栏的按钮,要能让图标放大,且要超出底部栏,实现一种越界的效果。
    上网搜索了一圈,都没有发现有类似的实现。(或许是我运气不好......)。
    所以就只有自己动手完成了,选中了android.support.design包下的TabLayout来实现。版本为:25.1.0

    最终效果图 这就是具体的效果

    第一次尝试


    把背景的上半部分做成透明的,使用了一个Layer-list,上面是透明背景,下面是红色的。这种效果做出来的时候,在平常时候真的跟透明没什么两样。但是如果底部栏的上方出现了可以滑动的组件,如ListView,RecycleView的时候,透明的区域就会出现。
    这样的效果肯定不可以的,所以这种想法被果断否决了。

    当上方有滑动组件时
    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item>
            <shape>
                <solid android:color="@android:color/transparent" />
            </shape>
        </item>
    
        <item android:top="7dp">
            <shape>
                <solid android:color="@android:color/holo_red_light" />
            </shape>
        </item>
    
    </layer-list>
    
    当时写的layer-list,上方为透明色,下方为红色

    第一次尝试失败后,就开始想其他的方法。
    有看到clipchildren属性,但是,对TabLayout使用clipchildren属性的时候,没有效果,不能让子View绘制出父容器以外。

    第二次尝试


    想到了属性动画来完成,可以如上,clipchildren属性无法使用,即使是做属性动画,平移Y坐标,子View依然无法绘制出父容器之外

    上网搜索的时候发现了一个思路,这一要好好感谢博客
    Android动画被父View遮挡的解决办法。
    他在这篇博客中提出了一个新的方法,这让我受到启发。然后,开始各种的折腾。写出了一个继承于TabLayout的子类。并在子类中封装了一系列方法,把TabLayout中的子View“复制”,放到根父类中,也就是容器android.id.content。

    主要的原理是,在TabLayout中View被绘制好后,获取这个具体View在屏幕中的坐标。同时获取根容器(父View)相对屏幕的坐标,请求新的被复制的View所在的位置,使用MarginParams,为其设置MarginTop和MarginLeft。点击时候又要GONE掉,最终还是完成了效果。

    源代码:

    /*
     * Copyright (c) 2016-2017 SLTPAYA
     */
    
    package xx.xx.xx.views;
    
    import android.animation.ObjectAnimator;
    import android.app.Activity;
    import android.content.Context;
    import android.os.Handler;
    import android.os.Message;
    import android.support.annotation.IdRes;
    import android.support.annotation.LayoutRes;
    import android.support.annotation.NonNull;
    import android.support.design.widget.CoordinatorLayout;
    import android.support.design.widget.TabLayout;
    import android.support.v7.app.ActionBar;
    import android.support.v7.app.AppCompatActivity;
    import android.util.AttributeSet;
    import android.util.DisplayMetrics;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.ViewParent;
    import android.widget.FrameLayout;
    import android.widget.ImageView;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    import org.sltpaya.tool.Utils;
    import java.util.ArrayList;
    
    import static org.sltpaya.tool.Utils.getDensity;
    
    public class FallHeadTabLayout extends TabLayout{
    
        private Tab mDefaultTab;
    //    private ArrayList<Item> mItems;
        private ArrayList<View> mCustomViews = new ArrayList<>();
        private ArrayList<View> mNormalViews = new ArrayList<>();
        private ArrayList<View> mPressedViews = new ArrayList<>();
        private int defaultSelectedPosition = 0;
    
        private LayoutInflater mInflater;
        private Context mContext;
    
        private OnTabSelectedListener mListener;
    
        private ImageView mIconView;
        private TextView mTextView;
        private ViewGroup mItemView;
        private int offestY;
    
        {
            mListener = new OnTabSelectedListener() {
                @Override
                public void onTabSelected(Tab tab) {
                    //按下时候:移除小视图,显示大视图
                    addView(tab,offestY,1);
                    removeView(tab.getPosition(),0);
                }
    
                @Override
                public void onTabUnselected(Tab tab) {
    //                tab.getCustomView().setVisibility(View.VISIBLE);
                    removeView(tab.getPosition(),1);
                    addView(tab,offestY,0);
                }
    
                @Override
                public void onTabReselected(Tab tab) {
    
                }
            };
        }
    
        public FallHeadTabLayout(Context context) {
            super(context);
        }
    
        public FallHeadTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public FallHeadTabLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public void addOnFallTabSelectedListener(@NonNull OnTabSelectedListener listener) {
            super.addOnTabSelectedListener(mListener);
            super.addOnTabSelectedListener(listener);
        }
    
        private void setDefaultListener(){
            super.addOnTabSelectedListener(mListener);
        }
    
    
        public static class Item{
    
            private int icon;
            private int selectIcon;
    
            private CharSequence tilte;
    
            private int textColor;
            private int selectTextColor;
    
            public Item( int icon, int selectIcon,CharSequence title,int textColor,int selectTextColor) {
                this.icon = icon;
                this.selectIcon = selectIcon;
                this.tilte = title;
                this.textColor = textColor;
                this.selectTextColor = selectTextColor;
            }
    
            public int getIcon() {
                return icon;
            }
    
            public CharSequence getTilte() {
                return tilte;
            }
    
            public int getSelectIcon() {
                return selectIcon;
            }
    
            public int getSelectTextColor() {
                return selectTextColor;
            }
    
            public int getTextColor() {
                return textColor;
            }
        }
    
        /**
         * 添加Item,没有按下时候的Item
         * @param item
         * @return
         */
        public FallHeadTabLayout addItem(Context context,Item item){
            mContext = context;
    //        mItems = new ArrayList<>();
    //        mItems.add(item);
            mInflater = LayoutInflater.from(context);
            initNormalView(item);
            initPressedView(item);
            aLiveView(item);
            return this;
        }
    
        /**
         * 使用自己的ItemView
         * @param itemView ViewGroup
         */
        public void setItemView(ViewGroup itemView,ImageView iconView,TextView textView){
            mItemView = itemView;
            mIconView = iconView;
            mTextView = textView;
        }
    
        /**
         * 使用自己的ItemView,传入id
         * @param layoutId Item的根布局
         * @param imgId Item中的Imageview id
         * @param textId Item中的TextView id
         */
        public void setItemView(@LayoutRes int layoutId,@IdRes int imgId,@IdRes int textId){
            mItemView = (ViewGroup) mInflater.inflate(layoutId,null);
            mIconView = (ImageView) mItemView.findViewById(imgId);
            mTextView = (TextView) mItemView.findViewById(textId);
        }
    
    
        private boolean useView(){
            if(mIconView==null||mTextView==null||mItemView==null){
                return true;
            }
            return false;
        }
    
    
        private void aLiveView(Item item){
            View view;
            ImageView img;
            TextView text;
    
            if(useView()){
                view = mInflater.inflate(R.layout.bottom_tab_item, null);
                img = (ImageView) view.findViewById(R.id.item_img);
                text = (TextView) view.findViewById(R.id.item_text);
            }else {
                view = mItemView;
                img = mIconView;
                text = mTextView;
            }
    
            img.setImageResource(item.getIcon());
            text.setText(item.getTilte());
            text.setTextColor(item.getTextColor());
            mCustomViews.add(view);
        }
    
        private void initNormalView(Item item){
            View view;
            ImageView img;
            TextView text;
    
            if(useView()){
                view = mInflater.inflate(R.layout.bottom_tab_item, null);
                img = (ImageView) view.findViewById(R.id.item_img);
                text = (TextView) view.findViewById(R.id.item_text);
            }else {
                view = mItemView;
                img = mIconView;
                text = mTextView;
            }
    
            img.setImageResource(item.getIcon());
            text.setText(item.getTilte());
            text.setTextColor(item.getTextColor());
            mNormalViews.add(view);
        }
    
        /**
         * 初始化所有的按下的视图!!!
         */
        private void initPressedView(Item item){
            View view;
            ImageView img;
            TextView text;
    
            if(useView()){
                view = mInflater.inflate(R.layout.bottom_tab_item, null);
                img = (ImageView) view.findViewById(R.id.item_img);
                text = (TextView) view.findViewById(R.id.item_text);
            }else {
                view = mItemView;
                img = mIconView;
                text = mTextView;
            }
    
            img.setImageResource(item.getSelectIcon());
            text.setText(item.getTilte());
            text.setTextColor(item.getSelectTextColor());
            mPressedViews.add(view);
        }
    
    //    /**
    //     * 设置默认被选中的
    //     * @param position Positon
    //     */
    //    public void setDefaultSelected(int position){
    //        defaultSelectedPosition = position;
    //    }
    
        /**
         * 初始化所有的Tab
         * @param offestY 整体布局Y轴偏移量,负值向上平移,正值向下平移
         */
        public void init(int offestY){
            this.offestY = dp2px(offestY);
            for (int i = 0; i < this.getTabCount(); i++) {
                TabLayout.Tab tab = this.getTabAt(i);
                if (tab != null) {
                    tab.setCustomView(mCustomViews.get(i));
    //                ObjectAnimator.ofFloat(tab.getCustomView(),"y",0,dp2px(offestY)).setDuration(1).start();//把Tab整体往上移动,因为布局问题
                    if(tab.getPosition()==defaultSelectedPosition){
                        mDefaultTab = tab;
                        tab.select();
                    }
                    tab.getCustomView().setVisibility(INVISIBLE);
                    setDefaultListener();
                }
            }
            //等待视图初始化完毕,获取到初始位置坐标,因为使用post和视图树监听时,没有办法设置
            this.post(new Runnable() {
                @Override
                public void run() {
                    setDefaultSelected();
                }
            });
        }
    
        public void setDefaultTab(int position){
            if(position<0){
                defaultSelectedPosition  = 0;
                return;
            }
            defaultSelectedPosition = position;
        }
    
        private int dp2px(int dp){
            DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
            float density = displayMetrics.density;
            int i =  (int) ( dp * density + 0.5f);
            System.out.println("6dp的高度是:"+i);
            return i;
        }
    
        //flag = 0为默认视图,为1时为按下视图
        private void addView(TabLayout.Tab tab,int offestY,int flag){
            //获取ActionBar高度
            int mActionBarHeight;
            if(mContext instanceof AppCompatActivity){
                ActionBar actionBar = ((AppCompatActivity)mContext).getSupportActionBar();
                if(actionBar==null) mActionBarHeight =0;
                else mActionBarHeight = actionBar.getHeight();
            }else {
                android.app.ActionBar actionBar = ((Activity)mContext).getActionBar();
                if(actionBar==null) mActionBarHeight =0;
                else mActionBarHeight = actionBar.getHeight();
            }
    
            //将按下状态的View添加到根View中
            int position = tab.getPosition();
            System.out.println("当前的Postion:"+position);
            View view;
            if(flag==0)
                view = mNormalViews.get(position);
            else if(flag==1)
                view = mPressedViews.get(position);
            else
                return;
            View mParentView = getRootView();
            ((ViewGroup)mParentView).addView(view);
    
            //获取原来View的位置
            ViewGroup group = (ViewGroup) tab.getCustomView();
            View raw_view = group.getChildAt(0);
            int[] location = new int[2];
            int[] pLocation = new int[2];
            mParentView.getLocationOnScreen(pLocation);
            raw_view.getLocationOnScreen(location);
            group.setVisibility(View.GONE);
    
            Log.d("FallHeadTabLayout","坐标X: "+location[0]+"坐标Y: "+location[1]+"父容器的位置:"+pLocation[0]+"Y: "+pLocation[1]);
            //设置新的位置
            MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
            params.leftMargin = location[0]-pLocation[0];
            System.out.println("actionBar的高度为:"+ mActionBarHeight);
            params.topMargin = location[1]- pLocation[1]+offestY;
    
        }
    
        //flag 0是默认视图,flag=1是按下视图
        private void removeView(int position,int flag){
            View view;
            if(flag==0)
                view = mNormalViews.get(position);
            else if(flag==1)
                view=mPressedViews.get(position);
            else
                return;
            getRootView().removeView(view);
        }
    
        /**
         * 获取到Activity的根ViewGroup对象
         * @return ViewGroup
         */
        public ViewGroup getRootView(){
            Activity activity = (Activity) mContext;
            ViewGroup content =  (ViewGroup) ((ViewGroup)activity.findViewById(android.R.id.content)).getChildAt(0);
            for (int i = 0; i < content.getChildCount(); i++) {
                View view = content.getChildAt(i);
                boolean b = view instanceof RelativeLayout;
                if(b){
                    return (ViewGroup) view;
                }
            }
            return content;
        }
    
    
        /**
         * set default selected position
         * must before setItemView()
         */
        public void setDefaultSelected(){
            int position = mDefaultTab.getPosition();
            for (int i = 0; i < this.getTabCount(); i++) {
                TabLayout.Tab tab = this.getTabAt(i);
                if(i!=position){
                    addView(tab,offestY,0);
                }
            }
            addView(mDefaultTab,offestY,1);
        }
    
    }
    

    布局文件:bottom_tab_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:clipChildren="false"
        android:layout_width="match_parent"
        android:gravity="center|top"
        android:layout_height="55dp">
    
        <ImageView
            android:id="@+id/item_img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/item_text"
            android:textSize="12sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <View
            android:background="@android:color/transparent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </LinearLayout>
    

    布局文件:main_activity

                    <FallHeadTabLayout
                        android:layout_gravity="bottom"
                        android:clipChildren="false"
                        android:background="@color/tab_bg"
                        android:id="@+id/bottom_navigation"
                        android:layout_width="match_parent"
                        android:layout_height="55dp"
                        app:tabBackground="@android:color/transparent"
                        app:tabIndicatorHeight="0dp"
                        app:tabMode="fixed"
                        app:tabPaddingEnd="15dp"
                        app:tabPaddingStart="15dp"
                        app:tabSelectedTextColor="#ffffff"
                        app:tabTextColor="#333333">
                    </FallHeadTabLayout>
    


    第三次

    尽管第二次完成了,但是总是感觉BUG很多,总是使用也感觉不好。所以,我开始了第三次的尝试

    这一次,我还是打算使用Clipchildren属性,将属性置为false

    但是在控件TabLayout中,尽管在根布局中设置了android:clipchildren="false",可是TabLayout还是无法溢出父容器。所以,我将TabLayout源码抽出,做了更改,最后,如愿以尝试的完成了。
    修改了TabLayout中以下部分源码:


    在TabLayout的构造方法中加入代码:setClipChildren(false);

    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            setClipChildren(false);
    

    在内部类SlidingTabStrip的构造方法中加入代码:setClipChildren(false);

     private class SlidingTabStrip extends LinearLayout {
            private int mSelectedIndicatorHeight;
            private final Paint mSelectedIndicatorPaint;
    
            int mSelectedPosition = -1;
            float mSelectionOffset;
    
            private int mIndicatorLeft = -1;
            private int mIndicatorRight = -1;
    
            private ValueAnimatorCompat mIndicatorAnimator;
    
            SlidingTabStrip(Context context) {
                super(context);
                setClipChildren(false);
                setWillNotDraw(false);
                mSelectedIndicatorPaint = new Paint();
            }   
    

    在内部类TabView的构造方法加入setClipChildren(false);,并注释掉:

        //            ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
        //              mTabPaddingEnd, mTabPaddingBottom);
    

     class TabView extends LinearLayout implements OnLongClickListener {
            private Tab mTab;
            private TextView mTextView;
            private ImageView mIconView;
    
            private View mCustomView;
            private TextView mCustomTextView;
            private ImageView mCustomIconView;
    
            private int mDefaultMaxLines = 2;
    
            public TabView(Context context) {
                super(context);
                if (mTabBackgroundResId != 0) {
                    ViewCompat.setBackground(
                            this, AppCompatResources.getDrawable(context, mTabBackgroundResId));
                }
    //            ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,
    //                    mTabPaddingEnd, mTabPaddingBottom);
                setClipChildren(false);
                setGravity(Gravity.BOTTOM);
                setOrientation(VERTICAL);
                setClickable(true);
                ViewCompat.setPointerIcon(this,
                        PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND));
            }
    

    最后在main_activity.xml布局,或者其他根布局文件总加入xml属性:android:clipchildren=flase即可。


    最后

    我封装了一个子类:可以更加方便快捷的实现底部栏的图标放大实现(溢出):
    项目中包含了用法的demo,上面有实现的图片
    上述图标均来自于【漫画岛应用】,此外淘宝也有类似的实现,只是没有找到相应的资源文件

    所有TabLayout中拥有的的功能,XTabLayout均保留,不会与android.support.design包的TabLayout冲突.

    github的地址:XTabLayout

    Gradle依赖库文件:

    在根目录,项目文件build.gradle文件中的allprojects节点中加入:maven { url 'https://jitpack.io' },如:
    
        allprojects {
            repositories {
                ...
                maven { url 'https://jitpack.io' }
            }
        }
    
    在module中(如:app:gradle)文件中的dependencies节点中加入:compile 'com.github.sltpaya:XTabLayout:25.1.1'如:
    
        dependencies {
                compile 'com.github.sltpaya:XTabLayout:25.1.1'
        }
    

    同步于android.support.design:TabLayout 25.1.0更新

    相关文章

      网友评论

      • 颜渃:不需要重写TabLayout,只需要把对应一层一层控件设置android:clipChildren="false"即可,比如:若想设置mTabStrip的属性,可以通过tabView的getParent获取,以此类推
        颜渃:if (customView.getParent() instanceof ViewGroup) {
        ViewGroup tabView = (ViewGroup) customView.getParent();
        tabView.setClipChildren(false);
        if (tabView.getParent() instanceof ViewGroup) {
        ((ViewGroup) tabView.getParent()).setClipChildren(false);
        }
        }
      • 全全是大王:问一下 为什么 图片把文字挤没了 不现实文字啊
      • 76ffef5441ab:你找到拉夫德鲁了 没, 海贼王
      • 毛毛虫撤回一条消息:弱弱的问下那个顶部突出的高度可以自己设置吗还是根据图片自身的高度固定的
        b3aa1bb26c60:看了下源码,是根据图片自身的高度确定的。TabLayout高度是固定的,用更大的图片顶部自然就会突出更多。

      本文标题:仿淘宝/漫画岛底部栏实现--解决TabLayout clipch

      本文链接:https://www.haomeiwen.com/subject/ntgiittx.html