• QQ
  • nahooten@sina.com
  • 常州市九洲新世界花苑15-2

Android

Android最佳底部导航栏,开源框架

原创内容,转载请注明原文网址:http://homeqin.cn/a/wenzhangboke/jishutiandi/Android/2019/0624/553.html

 
 
 
这个底部导航栏的特色:
 
 
 
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<Class> fragmentClassList = new ArrayList<>();
 
    private List<String> titleList = new ArrayList<>();
 
    private List<Integer> iconResBeforeList = new ArrayList<>();
 
    private List<Integer> iconResAfterList = new ArrayList<>();
 
 
 
    private List<Fragment> fragmentList = new ArrayList<>();
 
 
 
    private int itemCount;
 
 
 
    private Paint paint = new Paint();
 
 
 
    private List<Bitmap> iconBitmapBeforeList = new ArrayList<>();
 
    private List<Bitmap> iconBitmapAfterList = new ArrayList<>();
 
    private List<Rect> 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<Integer> 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.co妹妹it();
 
        }
 
    }
 
}
 
 
 
2.xml中
 
<com.example.bottombar.BottomBar
 
    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以及文本
 
 
 
就这么简单的代码,就搞定了统统!结果如下:
 
 
 
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的更改事件以及碎片的切换事件
 

上篇:上一篇:Android中处分溃散非常和记录日记
下篇:下一篇:两个Fragment跳转示例