android底层导航栏,Android最好用的底部导航栏,开源框架

这个底部导航栏的特点:

1.告别xml中的item布局,一切icon、title统统绘制得出;

2.扁平化,由于icon、title都是绘制得出的,所以只需要一个view即可,无需父布局

3.为你处理好碎片切换事务,告别冗余代码,让你从此光速开发

4.不怕需求变动,拔插式体验,增删item,只需修改1行代码

5.源代码十分简单,有助于使用者开发高度适配自身需求的底部

使用方式

1.只需要到给出的github地址中拷贝BottomBar类到你的包下即可,或者自己创建一个类名字叫BottomBar,复制如下代码并导包:

public class BottomBar extends View{

private Context context;

public BottomBar(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

this.context = context;

}

//

//提供的api 并且根据api做一定的物理基础准备

//

private int containerId;

private List fragmentClassList = new ArrayList<>();

private List titleList = new ArrayList<>();

private List iconResBeforeList = new ArrayList<>();

private List iconResAfterList = new ArrayList<>();

private List fragmentList = new ArrayList<>();

private int itemCount;

private Paint paint = new Paint();

private List iconBitmapBeforeList = new ArrayList<>();

private List iconBitmapAfterList = new ArrayList<>();

private List iconRectList = new ArrayList<>();

private int currentCheckedIndex;

private int firstCheckedIndex;

private int titleColorBefore = Color.parseColor("#999999");

private int titleColorAfter = Color.parseColor("#ff5d5e");

private int titleSizeInDp = 10;

private int iconWidth = 20;

private int iconHeight = 20;

private int titleIconMargin = 5;

public BottomBar setContainer(int containerId) {

this.containerId = containerId;

return this;

}

public BottomBar setTitleBeforeAndAfterColor(String beforeResCode, String AfterResCode) {//支持"#333333"这种形式

titleColorBefore = Color.parseColor(beforeResCode);

titleColorAfter = Color.parseColor(AfterResCode);

return this;

}

public BottomBar setTitleSize(int titleSizeInDp) {

this.titleSizeInDp = titleSizeInDp;

return this;

}

public BottomBar setIconWidth(int iconWidth) {

this.iconWidth = iconWidth;

return this;

}

public BottomBar setTitleIconMargin(int titleIconMargin) {

this.titleIconMargin = titleIconMargin;

return this;

}

public BottomBar setIconHeight(int iconHeight) {

this.iconHeight = iconHeight;

return this;

}

public BottomBar addItem(Class fragmentClass, String title, int iconResBefore, int iconResAfter) {

fragmentClassList.add(fragmentClass);

titleList.add(title);

iconResBeforeList.add(iconResBefore);

iconResAfterList.add(iconResAfter);

return this;

}

public BottomBar setFirstChecked(int firstCheckedIndex) {//从0开始

this.firstCheckedIndex = firstCheckedIndex;

return this;

}

public void build() {

itemCount = fragmentClassList.size();

//预创建bitmap的Rect并缓存

//预创建icon的Rect并缓存

for (int i = 0; i < itemCount; i++) {

Bitmap beforeBitmap = getBitmap(iconResBeforeList.get(i));

iconBitmapBeforeList.add(beforeBitmap);

Bitmap afterBitmap = getBitmap(iconResAfterList.get(i));

iconBitmapAfterList.add(afterBitmap);

Rect rect = new Rect();

iconRectList.add(rect);

Class clx = fragmentClassList.get(i);

try {

Fragment fragment = (Fragment) clx.newInstance();

fragmentList.add(fragment);

} catch (InstantiationException | IllegalAccessException e) {

e.printStackTrace();

}

}

currentCheckedIndex = firstCheckedIndex;

switchFragment(currentCheckedIndex);

invalidate();

}

private Bitmap getBitmap(int resId) {

BitmapDrawable bitmapDrawable = (BitmapDrawable) context.getResources().getDrawable(resId);

return bitmapDrawable.getBitmap();

}

//

//初始化数据基础

//

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

super.onLayout(changed, left, top, right, bottom);

initParam();

}

private int titleBaseLine;

private List titleXList = new ArrayList<>();

private int parentItemWidth;

private void initParam() {

if (itemCount != 0) {

//单个item宽高

parentItemWidth = getWidth() / itemCount;

int parentItemHeight = getHeight();

//图标边长

int iconWidth = dp2px(this.iconWidth);//先指定20dp

int iconHeight = dp2px(this.iconHeight);

//图标文字margin

int textIconMargin = dp2px(((float)titleIconMargin)/2);//先指定5dp,这里除以一半才是正常的margin,不知道为啥,可能是图片的原因

//标题高度

int titleSize = dp2px(titleSizeInDp);//这里先指定10dp

paint.setTextSize(titleSize);

Rect rect = new Rect();

paint.getTextBounds(titleList.get(0), 0, titleList.get(0).length(), rect);

int titleHeight = rect.height();

//从而计算得出图标的起始top坐标、文本的baseLine

int iconTop = (parentItemHeight - iconHeight - textIconMargin - titleHeight)/2;

titleBaseLine = parentItemHeight - iconTop;

//对icon的rect的参数进行赋值

int firstRectX = (parentItemWidth - iconWidth) / 2;//第一个icon的左

for (int i = 0; i < itemCount; i++) {

int rectX = i * parentItemWidth + firstRectX;

Rect temp = iconRectList.get(i);

temp.left = rectX;

temp.top = iconTop ;

temp.right = rectX + iconWidth;

temp.bottom = iconTop + iconHeight;

}

//标题(单位是个问题)

for (int i = 0; i < itemCount; i ++) {

String title = titleList.get(i);

paint.getTextBounds(title, 0, title.length(), rect);

titleXList.add((parentItemWidth - rect.width()) / 2 + parentItemWidth * i);

}

}

}

private int dp2px(float dpValue) {

float scale = context.getResources().getDisplayMetrics().density;

return (int) (dpValue * scale + 0.5f);

}

//

//根据得到的参数绘制

//

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);//这里让view自身替我们画背景 如果指定的话

if (itemCount != 0) {

//画背景

paint.setAntiAlias(false);

for (int i = 0; i < itemCount; i++) {

Bitmap bitmap = null;

if (i == currentCheckedIndex) {

bitmap = iconBitmapAfterList.get(i);

} else {

bitmap = iconBitmapBeforeList.get(i);

}

Rect rect = iconRectList.get(i);

canvas.drawBitmap(bitmap, null, rect, paint);//null代表bitmap全部画出

}

//画文字

paint.setAntiAlias(true);

for (int i = 0; i < itemCount; i ++) {

String title = titleList.get(i);

if (i == currentCheckedIndex) {

paint.setColor(titleColorAfter);

} else {

paint.setColor(titleColorBefore);

}

int x = titleXList.get(i);

canvas.drawText(title, x, titleBaseLine, paint);

}

}

}

//

//点击事件:我观察了微博和掌盟,发现down和up都在该区域内才响应

//

int target = -1;

@SuppressLint("ClickableViewAccessibility")

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN :

target = withinWhichArea((int)event.getX());

break;

case MotionEvent.ACTION_UP :

if (event.getY() < 0) {

break;

}

if (target == withinWhichArea((int)event.getX())) {

//这里触发点击事件

switchFragment(target);

currentCheckedIndex = target;

invalidate();

}

target = -1;

break;

}

return true;

//这里return super为什么up执行不到?是因为return super的值,全部取决于你是否

//clickable,当你down事件来临,不可点击,所以return false,也就是说,而且你没

//有设置onTouchListener,并且控件是ENABLE的,所以dispatchTouchEvent的返回值

//也是false,所以在view group的dispatchTransformedTouchEvent也是返回false,

//这样一来,view group中的first touch target就是空的,所以intercept标记位

//果断为false,然后就再也进不到循环取孩子的步骤了,直接调用dispatch-

// TransformedTouchEvent并传孩子为null,所以直接调用view group自身的dispatch-

// TouchEvent了

}

private int withinWhichArea(int x) { return x/parentItemWidth; }//从0开始

//

//碎片处理代码

//

private Fragment currentFragment;

//注意 这里是只支持AppCompatActivity 需要支持其他老版的 自行修改

protected void switchFragment(int whichFragment) {

Fragment fragment = fragmentList.get(whichFragment);

int frameLayoutId = containerId;

if (fragment != null) {

FragmentTransaction transaction = ((AppCompatActivity)context).getSupportFragmentManager().beginTransaction();

if (fragment.isAdded()) {

if (currentFragment != null) {

transaction.hide(currentFragment).show(fragment);

} else {

transaction.show(fragment);

}

} else {

if (currentFragment != null) {

transaction.hide(currentFragment).add(frameLayoutId, fragment);

} else {

transaction.add(frameLayoutId, fragment);

}

}

currentFragment = fragment;

transaction.commit();

}

}

}

2.xml中

android:background="#FFFFFF"

android:id="@+id/bottom_bar"

android:layout_width="match_parent"

android:layout_height="46dp"

android:layout_gravity="bottom" />

3.java代码中

BottomBar bottomBar = findViewById(R.id.bottom_bar);

bottomBar.setContainer(R.id.fl_container)

.setTitleBeforeAndAfterColor("#999999", "#ff5d5e")

.addItem(Fragment1.class,

"首页",

R.drawable.item1_before,

R.drawable.item1_after)

.addItem(Fragment2.class,

"订单",

R.drawable.item2_before,

R.drawable.item2_after)

.addItem(Fragment3.class,

"我的",

R.drawable.item3_before,

R.drawable.item3_after)

.build();

设置了容器frame layout

设置了字体选中前后的颜色

增加了item,并且给item绑定碎片,设定选中前后的drawable以及文本

就这么简单的代码,就搞定了一切!效果如下:

75f82b7d3f34

image.png

而如果你正常写一个底部导航栏是怎样的?

1.item布局,你还得精心设置半天

2.底部title布局,引入若干

3.title布局放到主布局中

4.java代码中要通过findViewById找到所有item

5.给所有item设置点击事件

6.点击事件内作碎片的切换

7.当你如果要增加一个item的时候,前面的又要大幅度修改,而且代码冗余程度极高

如果你对里面icon、title的位置不满意,有更多的api供你选择

setTitleSize,以dp为单位

setIconWidth,图标宽度

setIconHeight,图标高度

setTitleIconMargin,标题图标间距

setFirstChecked,设置第一个默认选中item

由于源代码简单,易于阅读,开发者更可以自行修改源码

底部导航栏设计思路

根据api中获取的参数,计算出icon、title的精确位置,并在onDraw中绘制

在onTouchEvent里,根据触摸点,获知点击区域,响应Icon、title的更改事件以及碎片的切换事件

另一个BottomBar的实战应用可以看:

再次证明了,BottomBar,实在太方便啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值