最近在写一个需求,要求点击底部栏的按钮,要能让图标放大,且要超出底部栏,实现一种越界的效果。
上网搜索了一圈,都没有发现有类似的实现。(或许是我运气不好......)。
所以就只有自己动手完成了,选中了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更新
网友评论
ViewGroup tabView = (ViewGroup) customView.getParent();
tabView.setClipChildren(false);
if (tabView.getParent() instanceof ViewGroup) {
((ViewGroup) tabView.getParent()).setClipChildren(false);
}
}