优化布局的几种方式
1. include/merge
布局优化中常常用到include/merge标签,include的含义类似C代码中的include,意思是直接把指定布局片段包含进当前的布局文件。include适用于多个布局文件中存在相同的xml片段,比如说相同的标题栏、相同的广告栏、相同的进度栏等等。
如:
<include layout="@layout/common_title" />
这时必定有个common_title.xml的布局文件,它用于在各页面展示相同的标题区域。
include子布局文件的根节点可以是LinearLayout或RelativeLayout或FrameLayout,可是上级布局文件往往已经有了相同的视图节点,这时子布局的根节点就变成冗余的了,但是布局文件又必须有根节点,着实矛盾。不要急,merge标签便是处理这个问题的,merge要和include配合使用,也就是说,merge只能是include子布局文件的根节点,且merge无需设置额外的属性。merge标签代替了根节点LinearLayout、RelativeLayout和FrameLayout原来的位置,只是告诉编译器:我是个占位的合并标签,不需要对我做布局处理;这样app在渲染UI时,只是简单合并merge标签下的内容,但不做布局计算和调整,从而提高了UI的加载效率。
2. ViewStub
在一个页面上根据不同条件展示不同的控件,我们常常会设置控件的可视属性,比如调用指定控件的setVisibility方法,若需展示则设置View.VISIBLE,若需隐藏则设置View.GONE。不过gone的控件只是看不到罢了,实际UI渲染时还是会被加载。要想事先不加载,在条件符合时才加载,就得用到标签ViewStub。
ViewStub类似一个简单的View,但具体布局由属性layout指定,并且在app加载UI时,ViewStub不显示界面内容,只有在代码中调用该控件的inflate方法,layout指定的布局才会展示。基于以上特性,ViewStub在提高布局性能上有几个特点:
优点:ViewStub在加载时只占用大约一个View控件的内存,不占用layout整个布局需要的内存;
缺点:ViewStub一旦调用inflate方法,页面内容就显示出来,之后就没有对应的方法再缩回去。如果还想再次隐藏或显示布局,只能通过setVisibility来实现。
举个ViewStub实际运用的场景,手机屏幕在竖屏和横屏切换时,有时希望显示不同的布局,比如竖屏显示列表,横屏则显示网格,横竖屏的截图如下:
竖屏的列表方式界面截图
横屏的网格方式界面截图
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#aaaaff"
android:paddingLeft="10dp"
android:paddingRight="10dp" >
<include layout="@layout/common_title" />
</RelativeLayout>
<ViewStub
android:id="@+id/vs_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/viewstub_list" />
<ViewStub
android:id="@+id/vs_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/viewstub_grid" />
</LinearLayout>
代码
import com.example.exmlayout.adapter.ContentGridAdapter;
import com.example.exmlayout.adapter.TitleListAdapter;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewStub;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
public class PlanetActivity extends Activity implements OnClickListener {
private static final String TAG = "PlanetActivity";
private String[] mStrList = {"水星", "金星", "地球", "火星", "木星", "土星"
, "天王星", "海王星", "冥王星"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_planet);
ImageButton ib_back = (ImageButton) findViewById(R.id.ib_back);
ib_back.setOnClickListener(this);
TextView tv_title = (TextView) findViewById(R.id.tv_title);
tv_title.setText("九大行星介绍");
Bundle bundle = getIntent().getExtras();
int type = bundle.getInt("type");
if (type == 0) {
Configuration config = getResources().getConfiguration();
if(config.orientation == Configuration.ORIENTATION_PORTRAIT){
showList();
} else {
showGrid();
}
} else if (type == 1) {
showList();
} else {
showGrid();
}
}
private void showList() {
ViewStub vs_list = (ViewStub) findViewById(R.id.vs_list);
vs_list.inflate();
ListView lv_hello = (ListView) findViewById(R.id.lv_hello);
TitleListAdapter adapter = new TitleListAdapter(this, mStrList);
lv_hello.setAdapter(adapter);
lv_hello.setOnItemClickListener(adapter);
lv_hello.setOnItemLongClickListener(adapter);
}
private void showGrid() {
ViewStub vs_grid = (ViewStub) findViewById(R.id.vs_grid);
vs_grid.inflate();
GridView gv_hello = (GridView) findViewById(R.id.gv_hello);
ContentGridAdapter adapter = new ContentGridAdapter(this, mStrList);
gv_hello.setAdapter(adapter);
gv_hello.setOnItemClickListener(adapter);
gv_hello.setOnItemLongClickListener(adapter);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.ib_back) {
finish();
}
}
}
3. style样式
样式在res/values/styles.xml中定义,它适用于下面几种情况:
1、布局文件中存在多个具有相同风格的控件,比如说统一的文本框TextView,都是白底黑字、中号字体、居中显示,这时我们便可在styles.xml定义一种文本样式,然后在各文本框处声明它的style属性。好处一个是减少了布局文件的大小,另一个是方便以后统一修改风格。
2、某些控件在代码中声明时需要手工指定style,例如自定义对话框需要在构造函数中指定样式,参见《Android开发笔记(六十六)自定义对话框》;另一个例子是弹窗PopupWindow在设置伸缩动画方法setAnimationStyle时需要指定动画样式,参见《Android开发笔记(六十五)多样的菜单》。
3、定义页面的主题风格,然后应用到Activity页面。代码中设置主题可通过“setTheme(R.style.)”完成,布局中设置可在AndroidManifest.xml的activity节点下添加theme属性,如“android:theme=”@style/“”。
下面是在styles.xml中自定义样式的一个例子
<style name="middle_text">
<item name="android:textColor">#000000</item>
<item name="android:textSize">17sp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="middle_text_center" parent="@style/middle_text">
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:gravity">center</item>
</style>
布局文件中给控件声明风格属性:
style="@style/middle_text_center"
4. Theme主题
主题是一种特殊的样式,主题专用于页面,而样式一般运用于控件。主题定义一般放在themes.xml,样式定义一般放在styles.xml。
Android定义了一些系统主题,完整定义的参见sdk自带的themes.xml,常用的几种说明如下:
Theme.NoTitleBar : 不显示标题栏,即隐藏ActionBar
Theme.Light : 白色背景
Theme.Holo : 浅灰背景
Theme.Black : 黑色背景
Theme.Wallpaper : 壁纸
Theme.Translucent : 透明背景
Theme.Dialog : 对话框
Theme.Panel : 平板
Theme.InputMethod : 输入法
Theme.SearchBar : 搜索框
在代码中给页面运用主题需要在所有视图初始化之前进行,也就是说,setTheme方法必须在setContentView方法之前执行。下面是个代码中设置主题的例子:
setTheme(android.R.style.Theme_Light_NoTitleBar);
在布局中运用主题,只需在activity界面下添加theme属性即可,下面是个布局中添加主题的例子:
android:theme="@android:style/Theme.Dialog"
除了系统自带的主题样式,我们也可以在themes.xml中自定义主题,具体步骤与自定义样式类似。下面是自定义主题时可能变更的窗口属性:
android:windowFrame : 窗口框架图像
android:windowBackground : 窗口背景
android:windowNoTitle : 窗口是否不要标题,即不带ActionBar
android:windowFullscreen : 窗口是否全屏
android:windowIsTranslucent : 窗口是否半透明
android:windowIsFloating : 窗口是否悬浮
android:windowAnimationStyle : 窗口切换动画的样式
android:windowEnterAnimation : 进入窗口的动画
android:windowExitAnimation : 退出窗口的动画
注意:windowFrame并不只是边框区域,还包括内部窗口,所以如果windowFrame设置为不透明的图像,那么内部窗口也将只显示这幅不透明的图像。
确实这三个属性容易混淆:android:windowFrame、android:windowBackground、android:background,文字描述感觉都说的不清楚,下面针对三个属性分别测试一下,看看究竟都是什么效果:
只有android:windowFrame设置为半透明红色的窗口截图:
从截图可以看到,windowFrame的覆盖区域包括窗口与边框,且窗口对内半透明、对外不透明,而边框对外半透明。
只有android:windowBackground设置为半透明红色的窗口截图:
从截图可以看到,windowBackground的覆盖区域只有窗口,且窗口对内对外都是半透明。
只有android:background设置为半透明红色的窗口截图:
从截图可以看到,background的覆盖区域只有窗口,且窗口对内半透明、对外不透明。