美文网首页UIAndroid UIandroid
手把手教你打造通用AppBar(附Demo)

手把手教你打造通用AppBar(附Demo)

作者: af83084249b7 | 来源:发表于2018-03-20 17:45 被阅读980次

    打造通用AppBar,解放你的时间.

    原因

    相信不少小伙伴在开发过程中都要用到ToolBar.但是SDK提供的ToolBar在很大程度上无法达到理想的扩展程度.我们不得不自己写ToolBar.

    思路

    每个页面的xml写一份AppBar肯定是不理想的做法.所以大部分小伙伴可能采用include的方式.确实这样开发效率和代码阅读能力都有了一定的提高.但是.能不能在此基础上再做优化呢?答案是肯定的啦.

    我们可以在include布局的基础上,把我们自定义ToolBar的控件获取、资源设置、事件处理全部封装起来,交给父类来处理.然后继承该父类(父类是我们自己写的,SDK没有哟,各位注意).

    这样我们的xml通过include进行了处理,activity中的大量逻辑也被父类进行了处理.我们只需要进行初始化方法的调用以及事件处理即可.
    先上效果图吧(看起来没什么,但是代码量极小哟)

    效果

    image

    解释

    我这边主要做了两种类型的封装.

    一:标准样式的(左边返回icon,中间标题,右边的可点击文本)

    二:自定义样式的(左边两个可点击icon,中间标题,右边两个可点击icon)

    不仅仅如此,背景、文本颜色都可自行修改,也可指定某些文本、icon是否显示(内部处理了statusBar颜色高度等问题).

    实现

    一:我们需要添加布局文件,通过include布局实现

      //标准样式:
      <include layout="@layout/common_appbar"/>
      //自定义样式
      <include layout="@layout/custom_appbar"/>
    

    二:接着继承AppBarActivity(标准样式)或者CustomAppBarActivity(自定义样式),里面封装了所有逻辑.但是它们是抽象的类,我们需要实现相关方法.

    //继承AppBarActivity要实现的方法
    //设置标题
    @Override
    protected String setAppBarTitle() {
        return "标准3";
    }
    //设置右边文本
    @Override
    protected String setAppBarRightTitle() {
        return "点我";
    }
    //设置返回键点击
    @Override
    protected void onAppBarBackClick() {
        finish();
    }
    //设置右边文本点击
    @Override
    protected void onAppBarRightClick() {
        Toast.makeText(this, "点击", Toast.LENGTH_SHORT).show();
    }
    ------------------------------分割------------------------------
    //继承CustomAppBarActivity要实现的方法
    //设置标题
     @Override
    protected String setAppBarTitle() {
        return "自定义2";
    }
    //设置icon资源(不显示的返回-1即可)
    @Override
    protected int[] setAppBarDrawableRes() {
        return new int[]{R.drawable.left_arrow, R.drawable.edit, R.drawable.no_star, R.drawable.share};
    }
    //四个icon的点击事件
    @Override
    protected void onAppBarClick(int position) {
        switch (position) {
            case 0:
                finish();
                break;
            case 1:
                Toast.makeText(this, "编辑", Toast.LENGTH_SHORT).show();
                break;
            case 2:
                if (mIsStared) {
                    Toast.makeText(this, "取消收藏", Toast.LENGTH_SHORT).show();
                    setThreeIVDrawable(R.drawable.no_star);
                } else {
                    Toast.makeText(this, "收藏", Toast.LENGTH_SHORT).show();
                    setThreeIVDrawable(R.drawable.star);
                }
                mIsStared = !mIsStared;
                break;
            case 3:
                Toast.makeText(this, "分享", Toast.LENGTH_SHORT).show();
                break;
        }
    }
    

    三:最后调用initAppBar() 进行最后的初始化.指定了颜色、是否显示等状态(需在setContentView()方法之后.
    注:initAppBar是个多参数重载方法,实现具体功能.

    四:父类实现代码(Demo下载即可,非重点了解对象)

    ///标准样式父类实现
    public abstract class AppBarActivity extends AppCompatActivity {
    
    protected void initAppBar() {
        initAppBar(true, false, -1, -1);
    }
    
    protected void initAppBar(boolean isBack) {
        initAppBar(isBack, false, -1, -1);
    }
    
    protected void initAppBar(boolean isBack, boolean isRightText) {
        initAppBar(isBack, isRightText, -1, -1);
    }
    
    protected void initAppBar(boolean isBack, boolean isRightText, @ColorRes int bgColor, @ColorRes int textColor) {
        RelativeLayout layout = (RelativeLayout) findViewById(R.id.common_appbar_rl);
        LinearLayout linearLayout = (LinearLayout) findViewById(R.id.common_appbar_ll);
        if (layout == null) {
            return;
        }
        invadeStatusBar();
        if (bgColor != -1) {
            linearLayout.setBackgroundResource(bgColor);
        }
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) layout.getLayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            layoutParams.setMargins(0, getStatusBarHeight(), 0, 0);
        } else {
            layoutParams.setMargins(0, 0, 0, 0);
        }
        layout.setLayoutParams(layoutParams);
        ImageView iconIV = (ImageView) findViewById(R.id.common_appbar_iv);
        if (!isBack) {
            iconIV.setVisibility(View.GONE);
        } else {
            iconIV.setVisibility(View.VISIBLE);
            iconIV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAppBarBackClick();
    
                }
            });
        }
        TextView centerTV = (TextView) findViewById(R.id.common_appbar_center_tv);
        if (textColor != -1) {
            centerTV.setTextColor(getResources().getColor(textColor));
        }
        centerTV.setText(TypeUtil.isBlank(setAppBarTitle()) ? "" : setAppBarTitle());
        TextView rightTV = (TextView) findViewById(R.id.common_appbar_right_tv);
        if (!isRightText) {
            rightTV.setVisibility(View.GONE);
        } else {
            rightTV.setVisibility(View.VISIBLE);
            if (textColor != -1) {
                centerTV.setTextColor(getResources().getColor(textColor));
            }
            rightTV.setText(TypeUtil.isBlank(setAppBarRightTitle()) ? "" : setAppBarRightTitle());
            rightTV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAppBarRightClick();
                }
            });
        }
    }
    
    
    protected abstract String setAppBarTitle();
    
    protected abstract String setAppBarRightTitle();
    
    protected abstract void onAppBarBackClick();
    
    protected abstract void onAppBarRightClick();
    
    protected void invadeStatusBar() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        }
    }
    
    protected int getStatusBarHeight() {
        Resources resources = getResources();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        int height = resources.getDimensionPixelSize(resourceId);
        Log.i("theHeight", height + "");
        if (height <= 10) {
            height = DensityUtil.dp2px(this, 16);
        }
        return height;
    }
    }
    
     ------------------------------分割----------------------------------------------
     
     //自定义样式父类实现
    public abstract class CustomAppBarActivity extends AppCompatActivity {
    private int[] mResArray;
    private ImageView mThreeIV;
    
    protected void initAppBar(boolean isOne, boolean isTwo, boolean isThree, boolean isFour) {
        initAppBar(isOne, isTwo, isThree, isFour, -1, -1);
    }
    
    protected void initAppBar(boolean isOne, boolean isTwo, boolean isThree, boolean isFour, @ColorRes int bgColor, @ColorRes int textColor) {
        RelativeLayout layout = (RelativeLayout) findViewById(R.id.custom_appbar_rl);
        LinearLayout linearLayout = (LinearLayout) findViewById(R.id.custom_appbar_ll);
        if (layout == null) {
            return;
        }
        invadeStatusBar();
        if (bgColor != -1) {
            linearLayout.setBackgroundResource(bgColor);
        }
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) layout.getLayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            layoutParams.setMargins(0, getStatusBarHeight(), 0, 0);
        } else {
            layoutParams.setMargins(0, 0, 0, 0);
        }
        layout.setLayoutParams(layoutParams);
        mResArray = setAppBarDrawableRes();
        TextView titleTV = (TextView) findViewById(R.id.custom_appbar_center_tv);
        if (textColor != -1) {
            titleTV.setTextColor(getResources().getColor(textColor));
        }
        titleTV.setText(setAppBarTitle() == null ? "默认标题" : setAppBarTitle());
        ImageView oneIV = (ImageView) findViewById(R.id.custom_appbar_one_iv);
        ImageView twoIV = (ImageView) findViewById(R.id.custom_appbar_two_iv);
        mThreeIV = (ImageView) findViewById(R.id.custom_appbar_three_iv);
        ImageView fourIV = (ImageView) findViewById(R.id.custom_appbar_four_iv);
        if (!isOne || mResArray.length < 1) {
            oneIV.setVisibility(View.GONE);
        } else {
            oneIV.setImageDrawable(getResources().getDrawable(mResArray[0]));
            oneIV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAppBarClick(0);
    
                }
            });
        }
        if (!isTwo || mResArray.length < 2) {
            twoIV.setVisibility(View.GONE);
        } else {
            twoIV.setImageDrawable(getResources().getDrawable(mResArray[1]));
            twoIV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAppBarClick(1);
                }
            });
        }
        if (!isThree || mResArray.length < 3) {
            mThreeIV.setVisibility(View.GONE);
        } else {
            mThreeIV.setImageDrawable(getResources().getDrawable(mResArray[2]));
            mThreeIV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAppBarClick(2);
                }
            });
        }
        if (!isFour || mResArray.length < 4) {
            fourIV.setVisibility(View.GONE);
        } else {
            fourIV.setImageDrawable(getResources().getDrawable(mResArray[3]));
            fourIV.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onAppBarClick(3);
                }
            });
        }
    }
    
    protected abstract String setAppBarTitle();
    
    protected abstract int[] setAppBarDrawableRes();
    
    protected abstract void onAppBarClick(int position);
    
    
    protected void setThreeIVDrawable(@DrawableRes int drawable) {
        if (mThreeIV == null) {
            return;
        }
        mThreeIV.setImageDrawable(getResources().getDrawable(drawable));
    }
    
    protected void invadeStatusBar() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = getWindow();
            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(Color.TRANSPARENT);
        }
    }
    
    private int getStatusBarHeight() {
        Resources resources = getResources();
        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
        int height = resources.getDimensionPixelSize(resourceId);
        Log.i("theHeight", height + "");
        if (height <= 10) {
            height = DensityUtil.dp2px(this, 16);
        }
        return height;
     }
     }
    

    通过以上设置我们处理了布局加载、控件初始化、事件处理.三大块处理完毕了.相信逻辑上大家都明白了.具体实现细节是在AppBarActivity以及CustomAppBarActivity父类里面进行了封装处理.大家可以根据自己具体业务选择继承父类.或者再此基础上面进行修改调整.使其符合自己项目业务需要.

    扩展

    大家有没有想过,这样重复的在每个xml布局include头布局也是挺烦人的。能不能优化呢?答案是肯定的,但是会有微小问题。

    代码实现

    下面注释的三行便是动态加载头布局的实现了,首先获取我们activity的最外层布局,然后将具体的头文件布局添加到最外层布局的第0个index位置。

        //在父类完成,子类无需重复操作
        //ViewGroup outView = (ViewGroup) ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0);
        // View inflate = LayoutInflater.from(this).inflate(R.layout.custom_appbar, null);
        //outView.addView(inflate,0);
        RelativeLayout layout = (RelativeLayout) findViewById(R.id.custom_appbar_rl);
        LinearLayout linearLayout = (LinearLayout) findViewById(R.id.custom_appbar_ll);
    
    动态加载布局问题

    该动态加载方法的唯一弊端,我们在写布局的时候,design预览页面会出现导航栏空白,如果是xml的include头布局方式则不会有这个问题。毕竟是代码动态加载布局的,此时未载入。当然这个也没有太大影响,使用方式因人而异吧。我会在仓库加入相关注释,大家可切换尝试。

    总结

    主要是思路的处理,以及细节封装.我这边有已经做好的Demo,可供大家交流以及直接使用.

    地址: https://github.com/HoldMyOwn/AppBar

    相关文章

      网友评论

      • Android之路:为什么不抽成通用的自定义View,在自定义View中去处理各个事情,不是更好么:joy:
        af83084249b7:两种方式都可以。但是涉及到状态栏处理、灵活性和代码阅读性等,我更推荐这种方式。
      • mcarthorlee:SB PM只会学ios,不知道MD
        TheShy_:这就是目前的现状啊 ....没辙
        af83084249b7:@mcarthorlee pm这个,不像技术,不好衡量,水准跨度大

      本文标题:手把手教你打造通用AppBar(附Demo)

      本文链接:https://www.haomeiwen.com/subject/nmibqftx.html