仿淘宝/漫画岛底部栏实现--解决TabLayout clipchildren属性无效问题--底部导航栏图标溢出实现

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

最终效果图
仿淘宝/漫画岛底部栏实现--解决TabLayout clipchildren属性无效问题--底部导航栏图标溢出实现_第1张图片
这就是具体的效果

第一次尝试


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

仿淘宝/漫画岛底部栏实现--解决TabLayout clipchildren属性无效问题--底部导航栏图标溢出实现_第2张图片
当上方有滑动组件时



    
        
            
        
    

    
        
            
        
    


当时写的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 mItems;
    private ArrayList mCustomViews = new ArrayList<>();
    private ArrayList mNormalViews = new ArrayList<>();
    private ArrayList 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




    
    
    



布局文件:main_activity

                
                


第三次

尽管第二次完成了,但是总是感觉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 clipchildren属性无效问题--底部导航栏图标溢出实现)