《一个Android工程的从零开始》阶段总结与修改5-BaseLayoutActivity

先扯两句

其实按照正常情况,这篇博客应该是在上一篇之前发的,实在是之前公司遇到了一个新需求,要创建一个背景透明的Activity做提示遮罩用,结果现有的BaseActivity布局的封装实在不支持这种情况,所以只能创建了一个不是基于BaseActivity的Activity已达成需求,虽然任务完成了,可是程序员探索的脚步不能停,于是痛定思痛之下,终于下定决心,对BaseActivity进行拆解,原本的BaseActivity保留所有的方法逻辑,而将与布局相关的方法与参数迁移到当前的BaseLayoutActivity中。
好了,闲言少叙,老规矩还是先上我的Git,然后开始正文吧。 MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)

正文

前面翻过android开发相关——include、merge和ViewStub的布局优化中已经阐述了ViewStub相关的内容,没有看到的朋友可以去看一下,而这里,我本次封装的引用就从include替换到了ViewStub。当然,这里也有一些需要提前说明,那就是一般而言,ViewStub使用的环境还是那些不常使用到的,例如引导页之类的功能,而title的使用频率还是蛮高的。而ViewStub解析所消耗的资源与无用的include占用的资源哪个更多,以我当前的水准还无法得到一个准确的答案,所以具体是使用ViewStub还是使用include还是看大家自己的理解了。当然,如果谁有更具体的数据,也欢迎分享,在此感激不尽。

BaseActivity的底层封装

首先,这里先看一下BaseActivity的布局xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.banshouweng.mybaseapplication.base.activity.BaseActivity">

    <ViewStub
        android:id="@+id/base_title_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/title_include" />

    <FrameLayout
        android:id="@+id/base_main_layout"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"/>
</LinearLayout>

其中只有两个控件:其一为在前面写的说过的ViewStub控件,在这里的主要作用就是封装的title布局文件;其二则是一个FrameLayout控件,其作用嘛,就是用以展示BaseActivity子类中重写getLayout()方法时传来id对应的布局文件。 实现方法,在《一个Android工程的从零开始》阶段总结与修改3-BaseActivity上(抽象处理)中,我们将BaseActivity做了抽象处理,其中使用到了一个方法在onCreate方法中调用了setBaseContentView(getLayoutId());如下图标注的位置。

image

而这个方法的具体实现如下。

/**
 * 引用头部布局
 *
 * @param layoutId 布局id
 */
private void setBaseContentView(int layoutId) {
    FrameLayout layout = getView(R.id.base_main_layout);

    //获取布局,并在BaseActivity基础上显示
    final View view = getLayoutInflater().inflate(layoutId, null);
    //关闭键盘
    hideKeyBoard();
    //给EditText的父控件设置焦点,防止键盘自动弹出
    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
    layout.addView(view, params);
}

可以看到,这里就是将传递来的子布局添加到FrameLayout中,而看过我之前博客,或者下载我封装的MyBaseApplication 的朋友会知道,当时使用的是LinearLayout而不是现在的FrameLayout。这真不是我闲的蛋疼,没事非要乱改,毕竟我没事就以懒汉自居,这是看过我博客的朋友都知道的,之所以改了名字完全是因为阿里没事非要发个什么《阿里巴巴Android开发手册》,为了生存嘛,肯定要向大公司开发规范靠拢喽。

四、UI 与布局

  1. 【推荐】灵活使用布局,推荐 Merge、ViewStub 来优化布局,尽可能多的减少 UI 布局层级,推荐使用 FrameLayout,LinearLayout、RelativeLayout 次之。

想必对于像我这种菜鸟而言,在使用布局的时候第一反应,还是使用的LinearLayout,等慢慢熟悉了之后忽然发现,很多情况下RelativeLayout比LinearLayout更好用,而现在又出来了一个ConstraintLayout。但是却很少有使用到FrameLayout的,或许大家在培训的时候知道,在创建Fragment的时候使用FrameLayout去占位,可一般老师也不说为什么。 之所以很少使用FrameLayout,并不是FrameLayout太难了,而是它太简单了,没办法完成我们所需求的那么多功能,而也正因为它的简单,才是我们这里使用它的原因,因为使用它可以减少不必要的资源浪费。通俗举例(但不一定完全正确):RelativeLayout的相对关系处理在这里就用不上,所以多加载了这些功能,就是浪费了资源。 其中除了加载布局就是在防止键盘弹出,当然,也不是去掉这几行代码就一定会弹出输入法,只是针对个别会自动弹出的机型做了一下统一,去掉也可以。 在子类中调用的时候,只需要重新抽象方法getLayoutId()即可:

@Override
protected int getLayoutId() {
    return R.layout.activity_main;
}

如图,上面就是在activity_base.xml中自定义的title,而下面的则是activity_main.xml的布局。

这里写图片描述

title封装

这里的title就是上面ViewStub对应的布局文件,title布局文件title_include.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/title_height">

    <RelativeLayout
        android:id="@+id/base_bg"
        android:layout_width="match_parent"
        android:layout_height="@dimen/title_height"
        android:background="@color/blue">

        <ImageView
            android:id="@+id/base_back"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:padding="@dimen/size_13"
            android:src="@mipmap/back"
            android:tint="@android:color/white" />

        <ImageView
            android:id="@+id/base_close"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:padding="@dimen/size_13"
            android:src="@mipmap/close"
            android:visibility="gone"
            android:layout_toRightOf="@+id/base_back"
            android:tint="@android:color/white" />

        <TextView
            android:id="@+id/base_title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:text="@string/title"
            android:textColor="@android:color/white"
            android:textSize="@dimen/size_20" />

        <ImageView
            android:id="@+id/base_right_icon2"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_toLeftOf="@+id/base_right_icon1"
            android:contentDescription="@string/second_function_key"
            android:padding="@dimen/size_13"
            android:src="@mipmap/add"
            android:tint="@android:color/white"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/base_right_icon1"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:contentDescription="@string/first_function_key"
            android:padding="@dimen/size_13"
            android:src="@mipmap/more"
            android:tint="@android:color/white"
            android:visibility="gone" />

        <TextView
            android:id="@+id/base_right_text"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:gravity="center"
            android:text="@string/make_sure"
            android:textColor="@android:color/white"
            android:textSize="@dimen/size_17"
            android:visibility="gone" />
    </RelativeLayout>
</RelativeLayout>
ViewStub的运用

开篇中已经提到了,我前面写的写的布局优化的内容,而关于ViewStub的运用,实际在其中已经阐述了,这里之所以还要拿出来说一下,不是有新内容,也不是水字数(毕竟也不是写网文),只是单纯的怕大家还需要去前面写的博客查代码比较麻烦而已,当然这里就不多废话了,直接把代码贴在这里就好了。

/**
 * Title ViewStub
 */
private ViewStub titleStub;

/**
 * 控件初始化
 */
protected void initBaseView() {
    if (titleStub == null) {
        titleStub = getView(R.id.base_title_layout);
        titleStub.inflate();
    }
}
关闭按钮

或许有人看到过我《一个Android工程的从零开始》-6、base(五) BaseFragment封装中,对于title的封装,其实若对比下来的话,实际上还是那些内容,只是比当初封装的时候多出了一个控件而已,就是:

<ImageView
    android:id="@+id/base_close"
    android:layout_width="50dp"
    android:layout_height="50dp"
    android:padding="@dimen/size_13"
    android:src="@mipmap/close"
    android:visibility="gone"
    android:layout_toRightOf="@+id/base_back"
    android:tint="@android:color/white" />

而这个控件就是下图中的“X”,在返回键的右侧,具体功能就是关闭:

这里写图片描述

关闭按钮的作用

可能会有人问,既然已经有了返回键,为什么还要添加一个关闭键,这样是不是有些多此一举。当然,在大多数情况下,这个关闭确实是用不到的,所以之前我也没设置这个按钮,可随着开发的项目越来越复杂,功能逐渐完善,就会发现,其实这个“X”的存在还是很有价值的,下面我就举两个例子:

1.在多级菜单一次关闭的情况下,我在京东上查笔记本电脑,目录如下:电脑、办公>电脑整机>笔记本>小米(MI) >小米Air,结果发现买不起,想要买一箱啤酒借酒消愁的时候,还需要一级一级返回到“电脑、办公”的上一级重新搜索(别说可以直接搜寻想要购买的商品,这里只是在说明目录结构复杂时的情况),而如果有了这个“关闭键”则可以直接关闭掉这一系列页面,直接跳转到首页去进行后续操作。

2.当使用WebView访问的时候,一般我们会在用户点击返回键(自定义返回键、物理返回键、或者虚拟返回键)的时候做如下代码处理:

if(webView.canGoBack()){  
    webView.goBack();  
    return true;  
}else{  
    finish(); 
    return false;  
} 

其目的就是当我们在H5页面中访问到如下目录时:电脑、办公>电脑整机>笔记本>小米(MI) >小米Air时,想要再查看其它小米产品,就可以直接点击返回键,而不是返回键直接退出了当前的WebView页面,不然如果我是用户,退出后做的第一件事就是把这个APP卸载了。可这样就会造成一个问题,当用户想退出WebView执行APP其他操作的时候,却需要将之前自己访问的页面都返回一遍,这样的用户体验会不会再次有一批想要卸载APP的呢?而这个时候,在我们无法智能到读取用户真实想法的情况下,就只能采取设置两个按钮的方法,一个是返回上一级,一个是关闭当前WebView页。

关闭按钮的实现

根据上面的分析,我们知道了关闭按钮常用的两种情况,而若说实现起来,也是两种情况。第一种情况下,点击关闭按钮的时候进行的是批量关闭Activity的操作;而第二种情况则只是简单的关闭当前WebView所在的Activity而已。

1.批量关闭Activity

/**
 * 关闭按钮
 */
private ImageView baseClose;

/**
 * 设置关闭部分页面
 *
 * @param targetActivity 关闭后所要返回的Activity对应的class
 */
public void setBaseClose(final Class<?> targetActivity) {
    initBaseView();
    baseClose = getView(R.id.base_close);
    baseClose.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            backTo(targetActivity);
        }
    });
}

首先initBaseView()方法自然是解析的title布局,随后是获取baseClose控件,并添点击事件。其中的backTo(targetActivity),是批量关闭Activity的方法,back的具体实现方法会在下一篇博客中详细说明。

2.关闭当前的Activity

/**
 * 设置关闭点击事件
 */
public void setBaseClose() {
    initBaseView();
    baseClose = getView(R.id.base_close);
    baseClose.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            finish();
        }
    });
}

看到这个方法打击一定会发现,这不就是上面的方法吗,若说区别就是一个传递了所要返回的Activity对应的class这个参数,并调用了backTo(targetActivity),另一个是直接finish的。或许可以做个判断如果传入的targetActivity为null则调用finish岂不是更好,何必重载一遍方法。以上的方法自然可以的,至于是否使用则需要看我们所开发的项目有多复杂。 不过从我封装BaseActivity的角度出发,关闭当前的Activity使用的却不是上面的这个方法,而是重载了下面的三个方法:

1.在使用的时候,考虑都方法或者控件的复用效果,虽然我们的设计初衷是“关闭按钮”,可是真正的开发过程中,谁也不知道产品会开个什么脑洞。而且即便不是产品故意为难我们,有一些页面在关闭的时候,难免要求弹窗提示用户“是否放弃当前页面的操作”,所以点击事件就不是一成不变的了,所以这里添加了一个boolean值,去做这个判断。也就是说,当我们需要重置点击事件的时候传入true即可,而不需要重置的时候,则传入false,就会调用finish方法了。

/**
 * 设置关闭点击事件
 *
 * @param isResetClose 是否重置关闭按钮点击事件
 */
public void setBaseClose(boolean isResetClose) {
    initBaseView();
    baseClose = getView(R.id.base_close);
    if (isResetClose) {
        baseClose.setOnClickListener(this);
    } else {
        baseClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });
    }
}

baseClose.setOnClickListener(this);是因为BaseActivity是实现了View.OnClickListener的抽象类 2.当前的重载方法同样是从重载的角度出发的,因为点击事件如果被重新定义的话,图标也可能会有所改变,这里只需要将对应图标的资源id传来即可。当然,既然重新设置图标,我就当你肯定会重新定义点击事件了,大家如果想完善个只换图标,点击事件不变的,可以自行添加(如果整个项目都统一换,请直接替换默认图片资源)。

/**
 * 设置关闭点击事件
 *
 * @param resId 重置关闭按钮图标
 */
public void setBaseClose(int resId) {
    initBaseView();
    baseClose = getView(R.id.base_close);
    baseClose.setImageResource(resId);
    baseClose.setOnClickListener(this);
}

3.不过当下单纯的使用关闭按钮的情况比较多,所以这里添加了一个重载方法,一般而言,我们直接调用这个方法即可,毕竟对于我这种懒汉来说,多写个false还是很麻烦的。

/**
 * 设置关闭点击事件
 */
public void setBaseClose() {
    setBaseClose(false);
}

或许有人会问,为什么批量关闭Activity的方法就没有这么多情况分析,难倒批量关闭Activity的时候就不存在自定义吗?答案自然是有的。至于为什么没有分析,难倒调用setBaseClose(true);不能重新定义批量关闭Activity的点击事件吗?

其实上述的方法就已经很全面了,不过下面这段也不算是狗尾续貂吧,只能说是被产品摧残多了以后的一种自觉的反应:

/**
 * 隐藏关闭按钮
 */
public void hideBaseClose() {
    if (null != baseClose){
        baseClose.setVisibility(View.GONE);
    } else {
        Logger.e(getName(),"baseClose is not exist");
    }
}
/**
 * 显示关闭按钮
 */
public void showBaseClose() {
    if (null != baseClose){
        baseClose.setVisibility(View.VISIBLE);
    } else {
        Logger.e(getName(),"baseClose is not exist");
    }
}

上面两个方法分别是关闭按钮的隐藏和显示方法,是在为了动态处理关闭按钮而添加的,一般情况下也使用不到,除非是产品说:当WebView中,刚进入H5页面时不需要显示关闭按钮,当经过操作,重新返回到首页时,由于返回键与关闭键功能重复,需要将关闭键隐藏的时候。至于为什么在baseClose不存在的时候,只是报错(Logger是我封装的Log)而没做其他法处理,实在是控件为空的原因,还需要特殊提醒吗?

title设置

这里写图片描述

之所以把上面这张图又贴出来,主要是想要说明一下,这里的title不是上图title的整体,主要说的是中间“MyTitle”位置的文本设置,当然,这里不过是有一个TextView而已,设置的方法很简单,不过是调用一个setText罢了,不过在调用setText的时候,大家有没有遇到过如下图的错误: 这里写图片描述

翻译过来就是我们想要查找的资源不存在,而这个资源是什么呢?

private int count = 0;

@SuppressLint("SetTextI18n")
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.merge_btn:
            view.setText(count++);
            break;
    }
}

也就是说,当我们设置的参数为数字的时候,这个数字会自动被当做资源id,所以说,在设置数字的时候,我们都需要将数字转换为字符串才能正常显示。不过,正是因为这个,所以上面我设置的“MyTitle”的时候,可以有两种方式:

// 字符串
title.setText("MyTitle");
<resources>
    <string name="title">MyTitle</string>
    ... 
</resources>

<--资源id 在res-->values-->strings.xml文件中-->

// 资源id
title.setText(R.string.title);

所以这里封装的setTitle方法也是有两部分:

/**
 * 设置标题
 *
 * @param title    标题的文本
 */
public void setTitle(String title) {
    initBaseView();
    ((TextView) getView(R.id.base_title)).setText(title);
}
/**
 * 设置标题
 *
 * @param titleId  标题的文本
 */
public void setTitle(int titleId) {
    initBaseView();
    ((TextView) getView(R.id.base_title)).setText(titleId);
}
返回键

返回键基本上是title的标配了,使用不到返回键的地方基本也就是首页罢了(启动页、引导页、登录页等页面不需要返回键的同时,也不需要title),所以这里返回键的初始化的部分就不单独列举方法了,而是与上面的setTitle融合在了一起,添加一个boolean值,用于设置是否需要展示返回键。

/**
 * 设置标题
 *
 * @param title    标题的文本
 * @param showBack 是否显示返回键
 */
public void setTitle(String title, boolean showBack) {
    initBaseView();
    ((TextView) getView(R.id.base_title)).setText(title);
    if (showBack) {
        baseBack = getView(R.id.base_back);
        baseBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

/**
 * 设置标题
 *
 * @param titleId  标题的文本
 * @param showBack 是否显示返回键(一般只用于首页)
 */
public void setTitle(int titleId, boolean showBack) {
    initBaseView();
    ((TextView) getView(R.id.base_title)).setText(titleId);
    if (showBack) {
        baseBack = getView(R.id.base_back);
        baseBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

当然,由于不显示返回键的情况过少,所以这里在外层又封装了两个方法,至于原因,自然是多写个true麻烦喽:

/**
 * 设置标题
 *
 * @param title 标题的文本
 */
public void setTitle(String title) {
    setTitle(title, true);
}
/**
 * 设置标题
 *
 * @param titleId 标题的文本
 */
public void setTitle(int titleId) {
    setTitle(titleId, true);
}

除此之外,返回键的图标也可能会做出修改,所以也定义了修改的方法:

/**
 * 重置返回点击事件
 *
 * @param resId 重置返回按钮图标
 */
public void setBaseBack(int resId) {
    initBaseView();
    baseBack = getView(R.id.base_back);
    baseBack.setImageResource(resId);
    baseBack.setOnClickListener(this);
}

再往复杂了考虑,就是在使用的时候,我们之前使用的时候是直接返回,可当满足一定条件,会重置返回键,这里也添加了重置的方法:

/**
 * 重置返回点击事件
 */
public void resetBaseBack() {
    if (null != baseBack) {
        baseBack.setOnClickListener(this);
    } else {
        Logger.e(getName(), "baseBack is not exist!");
    }
}
右侧功能键

这部分,在之前的博客《一个Android工程的从零开始》-2、base(一) BaseActivity布局中有所介绍,而本次也没有做出太多调整具体的可以去这篇博客中看,这里就只贴出代码效果图,以及对调整的地方做出说明了,先是在前面的布局优化博客中截出的效果图: 这里写图片描述

实现的方法如下:

/**
 * 最右侧图片功能键设置方法
 *
 * @param resId     图片id
 * @param alertText 语音辅助提示读取信息
 * @return 将当前ImageView返回方便进一步处理
 */
public ImageView setBaseRightIcon1(int resId, String alertText) {
    initBaseView();
    baseRightIcon1 = getView(R.id.base_right_icon1);
    baseRightIcon1.setImageResource(resId);
    baseRightIcon1.setVisibility(View.VISIBLE);
    //语音辅助提示的时候读取的信息
    baseRightIcon1.setContentDescription(alertText);
    baseRightIcon1.setOnClickListener(this);
    return baseRightIcon1;
}

/**
 * 右数第二个图片功能键设置方法
 *
 * @param resId     图片id
 * @param alertText 语音辅助提示读取信息
 * @return 将当前ImageView返回方便进一步处理
 */
public ImageView setBaseRightIcon2(int resId, String alertText) {
    ImageView baseRightIcon2 = getView(R.id.base_right_icon2);
    if (null != baseRightIcon1) {
        baseRightIcon2.setImageResource(resId);
        baseRightIcon2.setVisibility(View.VISIBLE);
        //语音辅助提示的时候读取的信息
        baseRightIcon2.setContentDescription(alertText);
        baseRightIcon2.setOnClickListener(this);
    } else {
        Logger.e(getName(),"You must inflate the baseRightIcon1 before use baseRigh
    }
    return baseRightIcon2;
}

/**
 * 最右侧文本功能键设置方法
 *
 * @param text 文本信息
 * @return 将当前TextView返回方便进一步处理
 */
public TextView setBaseRightText(String text) {
    initBaseView();
    TextView baseRightText = getView(R.id.base_right_text);
    baseRightText.setText(text);
    baseRightText.setVisibility(View.VISIBLE);
    baseRightText.setOnClickListener(this);
    return baseRightText;
}

/**
 * 最右侧文本功能键设置方法
 *
 * @param textId 文本信息id
 * @return 将当前TextView返回方便进一步处理
 */
public TextView setBaseRightText(int textId) {
    initBaseView();
    TextView baseRightText = getView(R.id.base_right_text);
    baseRightText.setText(textId);
    baseRightText.setVisibility(View.VISIBLE);
    baseRightText.setOnClickListener(this);
    return baseRightText;
}

这里与之前不同的地方只有setBaseRightIcon2,因为在布局中,有下图所示的关系: 这里写图片描述

也就是说,BaseRightIcon2需要在BaseRightIcon1的基础上确定位置,所以在没有调用setBaseRightIcon1便直接调用setBaseRightIcon2给出错误提示。

其他

其他还有两个方法:

/**
 * 隐藏头布局
 */
public void hideTitle() {
    if (titleStub != null) {
        titleStub.setVisibility(View.GONE);
    }
}

/**
 * 隐藏返回键
 */
public void hideBack() {
    if (null != baseBack) {
        baseBack.setVisibility(View.GONE);
    } else {
        Logger.e(getName(), "baseBack is not exist!");
    }
}

这两个方法是之前使用include添加布局的时候使用的方法,一般逻辑下,用不到上面两个方法,不过暂时没有删除,后期看情况处理。

附录

《一个Android工程的从零开始》- 目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值