最近看到很多app中间icon凸起,比如小米商城、一淘、菜鸟等等,正好也在复习自定view的知识,就写了这篇文章。
框架设计思想
- 功能模块 高内聚低耦合
- 面向接口(接口先行)
- 要易于扩展和维护(面向未来)
需求分析
- 可以提供提供通用的API
- 支持透明度和底部透出
- 支持Tab中间高度超过,凸起布局效果
- 支持iconfont和Bitmap和url
实现思路
通过resetHeight达到底部凸起效果
public class TabBottomG extends RelativeLayout implements ITabG<TabBottomInfo<?>> {
private TabBottomInfo<?> tabInfo;
private ImageView tabImageView;
private TextView tabIconView;
private TextView tabNameView;
public TabBottomG(Context context) {
this(context,null);
}
public TabBottomG(Context context,AttributeSet attrs) {
this(context,attrs,0);
}
public TabBottomG(Context context,AttributeSet attrs,int defStyleAttr) {
super(context,attrs,defStyleAttr);
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.tab_bottom_g, this);
tabImageView = findViewById(R.id.iv_image);
tabIconView = findViewById(R.id.tv_icon);
tabNameView = findViewById(R.id.tv_name);
}
@Override
public void setTabInfoG(@NonNull @NotNull TabBottomInfo<?> tabBottomInfo) {
this.tabInfo = tabBottomInfo;
inflateInfo(false, true);
}
public TabBottomInfo getTabInfo() {
return tabInfo;
}
public ImageView getTabImageView() {
return tabImageView;
}
public TextView getTabIconView() {
return tabIconView;
}
public TextView getTabNameView() {
return tabNameView;
}
/**
* 改变某个tab的高度
*
* @param height
*/
@Override
public void resetHeight(@Px int height) {
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.height = height;
setLayoutParams(layoutParams);
getTabNameView().setVisibility(View.GONE);
}
private void inflateInfo(boolean selected, boolean init) {
if (tabInfo.tabType == TabBottomInfo.TabType.ICON) {
if (init) {
tabImageView.setVisibility(GONE);
tabIconView.setVisibility(VISIBLE);
Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), tabInfo.iconFont);
tabIconView.setTypeface(typeface);
if (!TextUtils.isEmpty(tabInfo.name)) {
tabNameView.setText(tabInfo.name);
}
}
if (selected) {
tabIconView.setText(TextUtils.isEmpty(tabInfo.selectedIconName) ? tabInfo.defaultIconName : tabInfo.selectedIconName);
tabIconView.setTextColor(getTextColor(tabInfo.tintColor));
tabNameView.setTextColor(getTextColor(tabInfo.tintColor));
} else {
tabIconView.setText(tabInfo.defaultIconName);
tabIconView.setTextColor(getTextColor(tabInfo.defaultColor));
tabNameView.setTextColor(getTextColor(tabInfo.defaultColor));
}
} else if (tabInfo.tabType == TabBottomInfo.TabType.BITMAP) {
if (init) {
tabImageView.setVisibility(VISIBLE);
tabIconView.setVisibility(GONE);
if (!TextUtils.isEmpty(tabInfo.name)) {
tabNameView.setText(tabInfo.name);
}
}
if (selected) {
//tabImageView.setImageBitmap(tabInfo.selectedBitmap);
//tabImageView.setImageResource(R.drawable.luoxi);
tabImageView.setImageResource(tabInfo.selectedResId);
} else {
//tabImageView.setImageBitmap(tabInfo.defaultBitmap);
tabImageView.setImageResource(tabInfo.defaultResId);
}
}
}
@Override
public void onTabSelectedChange(int index,@Nullable @org.jetbrains.annotations.Nullable TabBottomInfo<?> prevInfo,@NonNull @NotNull TabBottomInfo<?> nextInfo) {
if (prevInfo != tabInfo && nextInfo != tabInfo || prevInfo == nextInfo) {
return;
}
if (prevInfo == tabInfo) {
inflateInfo(false, false);
} else {
inflateInfo(true, false);
}
}
/**
* color可传string或int的方法
*/
@ColorInt
private int getTextColor(Object color) {
if (color instanceof String) {
return Color.parseColor((String) color);
} else {
return (int) color;
}
}
}
外部容器封装
/**
* Desc:
* author:Christiano
* 1. 透明度和底部透出,列表可渲染高度问题
* 2. 中间高度超过,凸起布局
*/
public class TabBottomLayoutG extends FrameLayout implements ITabLayout<TabBottomG,TabBottomInfo<?>> {
private List<OnTabSelectedListener<TabBottomInfo<?>>> tabSelectedChangeListeners = new ArrayList<>();
private TabBottomInfo<?> selectedInfo;
private float bottomAlpha = 1f;
//TabBottom高度
private static float tabBottomHeight = 50;
//TabBottom的头部线条高度
private float bottomLineHeight = 0.5f;
//TabBottom的头部线条颜色
private String bottomLineColor = "#dfe0e1";
private List<TabBottomInfo<?>> infoList;
//底部背景色
private int bottomBgColor = Color.WHITE;
private static final String TAG_TAB_BOTTOM = "TAG_TAB_BOTTOM";
public TabBottomLayoutG(@NonNull Context context) {
this(context, null);
}
public TabBottomLayoutG(@NonNull Context context,@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public TabBottomLayoutG(@NonNull Context context,@Nullable AttributeSet attrs,int defStyleAttr) {
super(context,attrs,defStyleAttr);
}
@Override
public void inflateInfo(@NonNull @NotNull List<TabBottomInfo<?>> infoList) {
if (infoList.isEmpty()) {
return;
}
this.infoList = infoList;
// 移除之前已经添加的View
for (int i = getChildCount() - 1; i > 0; i--) {
removeViewAt(i);
}
selectedInfo = null;
addBackground();
//清除之前添加的HiTabBottom listener,Tips:Java foreach remove问题
Iterator<OnTabSelectedListener<TabBottomInfo<?>>> iterator = tabSelectedChangeListeners.iterator();
while (iterator.hasNext()) {
if (iterator.next() instanceof TabBottomG) {
iterator.remove();
}
}
int height = DisplayUtil.dp2px(tabBottomHeight, getResources());
FrameLayout ll = new FrameLayout(getContext());
int width = DisplayUtil.getDisplayWidthInPx(getContext()) / infoList.size();
ll.setTag(TAG_TAB_BOTTOM);
for (int i = 0; i < infoList.size(); i++) {
final TabBottomInfo<?> info = infoList.get(i);
//Tips:为何不用LinearLayout:当动态改变child大小后Gravity.BOTTOM会失效
LayoutParams params = new LayoutParams(width, height);
params.gravity = Gravity.BOTTOM;
params.leftMargin = i * width;
TabBottomG tabBottom = new TabBottomG(getContext());
tabSelectedChangeListeners.add(tabBottom);
tabBottom.setTabInfoG(info);
ll.addView(tabBottom, params);
tabBottom.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onSelected(info);
}
});
}
LayoutParams flPrams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
flPrams.gravity = Gravity.BOTTOM;
addBottomLine();
addView(ll, flPrams);
fixContentView();
}
@Override
public void addTabSelectedChangeListener(OnTabSelectedListener<TabBottomInfo<?>> listener) {
tabSelectedChangeListeners.add(listener);
}
public void setTabAlpha(float alpha) {
this.bottomAlpha = alpha;
}
public void setBottomBgColor(int color) {
this.bottomBgColor = color;
}
public void setTabHeight(float tabHeight) {
this.tabBottomHeight = tabHeight;
}
public void setBottomLineHeight(float bottomLineHeight) {
this.bottomLineHeight = bottomLineHeight;
}
public void setBottomLineColor(String bottomLineColor) {
this.bottomLineColor = bottomLineColor;
}
@Override
public TabBottomG findTab(@NonNull @NotNull TabBottomInfo<?> info) {
ViewGroup ll = findViewWithTag(TAG_TAB_BOTTOM);
for (int i = 0; i < ll.getChildCount(); i++) {
View child = ll.getChildAt(i);
if (child instanceof TabBottomG) {
TabBottomG tab = (TabBottomG) child;
if (tab.getTabInfo() == info) {
return tab;
}
}
}
return null;
}
@Override
public void defaultSelected(@NonNull @NotNull TabBottomInfo<?> defaultInfo) {
onSelected(defaultInfo);
}
private void onSelected(@NonNull TabBottomInfo<?> nextInfo) {
for (OnTabSelectedListener<TabBottomInfo<?>> listener : tabSelectedChangeListeners) {
listener.onTabSelectedChange(infoList.indexOf(nextInfo), selectedInfo, nextInfo);
}
this.selectedInfo = nextInfo;
}
private void addBottomLine() {
View bottomLine = new View(getContext());
bottomLine.setBackgroundColor(Color.parseColor(bottomLineColor));
LayoutParams bottomLineParams =
new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtil.dp2px(bottomLineHeight, getResources()));
bottomLineParams.gravity = Gravity.BOTTOM;
bottomLineParams.bottomMargin = DisplayUtil.dp2px(tabBottomHeight - bottomLineHeight, getResources());
addView(bottomLine, bottomLineParams);
bottomLine.setAlpha(bottomAlpha);
}
private void addBackground() {
View view = LayoutInflater.from(getContext()).inflate(R.layout.bottom_layout_bg, null);
LayoutParams params =
new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DisplayUtil.dp2px(tabBottomHeight, getResources()));
params.gravity = Gravity.BOTTOM;
addView(view, params);
view.setAlpha(bottomAlpha);
view.setBackgroundColor(bottomBgColor);
}
/**
* 修复内容区域的底部Padding
*/
private void fixContentView() {
if (!(getChildAt(0) instanceof ViewGroup)) {
return;
}
ViewGroup rootView = (ViewGroup) getChildAt(0);
ViewGroup targetView = ViewUtil.findTypeView(rootView, RecyclerView.class);
if (targetView == null) {
//查找srcollview
targetView = ViewUtil.findTypeView(rootView, ScrollView.class);
}
if (targetView == null) {
//查找AbsListView(是gridview和listview子类)
targetView = ViewUtil.findTypeView(rootView, AbsListView.class);
}
if (targetView != null) {
targetView.setPadding(0, 0, 0, DisplayUtil.dp2px(tabBottomHeight, getResources()));
targetView.setClipToPadding(false);
}
}
}