摆脱XML布局文件

相信每一个Android开发者,在接触“Hello World”的时候,就形成了一个观念:Android UI布局是通过layout目录下的XML文件定义的。使用XML定义布局的方式,有着结构清晰、可预览等优势,因而极为通用。可是,偏偏在某些场景下,布局是需要根据运行时的状态变化的,无法使用XML预先定义。这时候,我们只能通过JavaCode控制,在程序运行时,动态的实现对应的布局。

所以,作为入门,将从给三个方面给大家介绍一些动态布局相关的基础知识和经验。

  • 动态添加view到界面上,摆脱layout文件夹下的XML文件。
  • 熟悉Drawable子类,摆脱drawable文件夹下的XML文件。

NinePatchChunk

  • ,解析如何实现后台下发.9图片给客户端使用。

动态添加View

这一步,顾名思义,就是把我们要的View添加到界面上去。这是动态布局中最基础最常用的步骤。

ButtonImageViewRelativeLayoutLinearLayout等等元素最终都是继承于View这个类的。按照我自己的理解,可以将它们分为两类,控件和容器(这两个名字纯属作者自己编的,并非官方定义)。ButtonImageView这类直接继承于View的就是控件,控件一般是用来呈现内容和与用户交互的;RelativeLayoutLinearLayout这类继承于ViewGroup的就是容器,容器就是用来装东西的。Android是嵌套式布局的设计,因此,容器装的既可以是容器,也可以是控件。

更直接的,还是通过一段demo代码来看吧。

setContentView(R.layout.xxx)了,我们需要先添加一个root作为整个的容器,

RelativeLayout root = new RelativeLayout(this);
root.setBackgroundColor(Color.WHITE);
setContentView(root, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

然后,我们尝试在屏幕正中间添加一个按钮,

Button button1 = new Button(this);
button1.setId(View.generateViewId());
button1.setText("Button1");
button1.setBackgroundColor(Color.RED);
LayoutParams btnParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
btnParams.addRule(RelativeLayout.CENTER_IN_PARENT, 1);
root.addView(button1, btnParams);

root里面了,new Button(this)

  • ,并初始化控件相关的属性。

root

  • 的类型,

new LayoutParams

  • ,这个参数主要用来描述要添加的

view

  • 在容器中的定位信息,包括高宽,居中对齐,margin等等属性。特别地,对于上面的例子,相对于父容器居中的实现是,

btnParams.addRule(RelativeLayout.CENTER_IN_PARENT, 1)

  • ,这里对应XML的代码则是

android:centerInParent='true'

root.addView(button1, btnParams)

  • 就行了。

TextViewButton,而且各自占的宽度比例为2:3(对于android:layout_weight属性),demo代码如下,

// 在按钮右下方添加一个线性布局
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
LayoutParams lParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
lParams.addRule(RelativeLayout.BELOW, button1.getId());
lParams.addRule(RelativeLayout.RIGHT_OF, button1.getId());
root.addView(linearLayout, lParams);

// 在线性布局中,添加一个TextView和一个Button,宽度按2:3的比例
TextView textView = new TextView(this);
textView.setText("TextView");
textView.setTextSize(28);
textView.setBackgroundColor(Color.BLUE);
LinearLayout.LayoutParams tParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT);
tParams.weight = 2;    // 定义宽度的比例
linearLayout.addView(textView, tParams);

Button button2 = new Button(this);
button2.setText("Button2");
button2.setBackgroundColor(Color.RED);
LinearLayout.LayoutParams bParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT);
bParams.weight = 3; // 定义宽度的比例
linearLayout.addView(button2, bParams);

需要注意的是,上面代码中的lParams.addRule(RelativeLayout.BELOW, button1.getId())XML对应android:layout_belowbutton1)的id不为0,否则规则会失效。通常,为了防止id重复,建议使用系统方法来生成id,也就是第二段代码中的button1.setId(View.generateViewId())

最终,这一段代码执行下来,我们得到的效果就是,


android动态加载xml文件路径 安卓动态加载xml布局_控件


但是,添加view作者也遇到过一个小小坑。RelativeLayout右边添加一个ImageView,同时,这个ImageView的右边部分在RelativeLayout的外面。

android动态加载xml文件路径 安卓动态加载xml布局_控件_02

一开始,作者的代码如下,却只能得到上图右边的效果,

ImageView imageView = new ImageView(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
params.leftMargin = x;    // 到左边的距离
params.topMargin = y;    // 到上边的距离
parent.addView(imageView, params);

onMeasureonLayout的时候,受到了rightMarginparams.rightMargin = -1*width就可以了。(有兴趣的同学可以去看看源码,这里就不详解了)

Drawable子类

selectorshape等等。可是,考虑到一个场景:selector里面引用的图片,不是打包时res目录的资源,而是后台下发的图片呢?类似场景下,我们能不能摆脱这类XML文件呢?XML定义能实现的,Java代码一定能够实现。从drawable的目录名就可以看出,不管是selectorshape或是其他,总归都应该是drawable。因此,在Java代码中,总应该有一个Drawable的子类来对应他们。下面,就介绍几个常用的Drawable的子类给大家。StateListDrawable:对应selector,主要用来描述按钮等的点击态。

StateListDrawable selector = new StateListDrawable();
btnSelectorDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePress);
btnSelectorDrawable.addState(new int[]{android.R.attr.state_enabled}, drawableEnabel);
btnSelectorDrawable.addState(new int[]{android.R.attr.state_selected}, drawableSelected);
btnSelectorDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused);
btnSelectorDrawable.addState(new int[]{}, drawableNormal);

GradientDrawable:对应渐变色

GradientDrawable drawable = new GradientDrawable();
drawable.setOrientation(Orientation.TOP_BOTTOM); //定义渐变的方向
drawable.setColors(colors); //colors为int[],支持2个以上的颜色

最后,说一个比较复杂的Drawable,是进度条相关的。

LayerDrawable:对应Seekbar android:progressDrawable

通常,我们用XML定义一个进度条的ProgressDrawable是这样的,

<!--ProgressDrawable-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background" android:drawable="@drawable/background"/>
    <item android:id="@android:id/secondaryProgress" android:drawable="@drawable/secondary_progress"/>
    <item android:id="@android:id/progress" android:drawable="@drawable/progress"/>
</layer-list>

@drawable/progress@drawable/secondary_progress也不是普通的drawable,

<!--@drawable/progress 定义-->
<clip xmlns:android="http://schemas.android.com/apk/res/android"
      android:clipOrientation="horizontal"
      android:drawable="@drawable/progress_drawable"
      android:gravity="left" >
</clip>

ProgressDrawable,我们需要定义多个XML文件的,还是比较复杂的。那么JavaCode实现呢?

其实,理解了XML实现的方式,下面的JavaCode就很好理解了。

LayerDrawable layerDrawable = (LayerDrawable) getProgressDrawable();

//背景
layerDrawable.setDrawableByLayerId(android.R.id.background, backgroundDrawable);

//进度条
ClipDrawable clipProgressDrawable = new ClipDrawable(progressDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
layerDrawable.setDrawableByLayerId(android.R.id.progress, clipProgressDrawable);

//缓冲进度条
ClipDrawable clipSecondaryProgressDrawable = new ClipDrawable(secondaryProgressDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
layerDrawable.setDrawableByLayerId(android.R.id.secondaryProgress, clipSecondaryProgressDrawable);

Drawable的子类,大家可以根据自己需求去官方文档上查询就行了。