在做AndroidScreenSlidePager开源库练习demo的时候,发现布局文件使用的是<merge>标签而不是<FrameLayout>标签。作者给出的说法是:CirclePageIndicator
and ViewPager
shoud be used as child views of a Framelayout
. But here we used merge
instead, because the root view in any activity is a FrameLayout
.【翻译:CirclePageIndicator
and ViewPager
必须是作为根节点为FrameLayout的子节点,但是这里我们使用merge替换Framelayout,因为所有的Activity视图的根节点都是FrameLayout。】
Activity中的onCreate方法中的setContentView(R.layout.main);代表的含义是:我们放置的main.xml布局文件被放置在一个id为content的FrameLayout的布局中,这也就是为啥Activity的setContentView方法叫set content view了,就是把我们的xml放入了这个id为content的FrameLayout中。
一、Merge对布局的优化以及示例
<merge/>的出现是为了优化android布局,减少视图树的层级,过多的层级也会导致程序变慢。当LayoutInflater遇到这个标签时,它会跳过它,并将<merge />内的元素添加到<merge />的父元素里。
(1)布局文件:根节点为<FrameLayout>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" android:layout_marginBottom="20dip" android:background="#AA000000" android:padding="12dip" android:text="Golden Gate" android:textColor="#ffffffff" /> </FrameLayout>
activity中的代码:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); }
效果图:
使用HierarchyViewer 工具来查看该视图的层级效果,我们可以看到蓝色的矩形的就是我们刚刚的FrameLayout的层数。
(2)布局文件:根节点为<merge>
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ImageView android:id="@+id/goldenIv" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" android:layout_marginBottom="20dip" android:background="#AA000000" android:padding="12dip" android:text="Golden Gate" android:textColor="#ffffffff" /> </merge>
activity中的代码:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main2);
}
我们看到的效果是这样的,蓝色的是用来包含之前FrameLayout的父标签,现在直接包裹着ImageView和TextView两个子标签。可以理解为将merge标签中的子集直接加到Activity的FrameLayout跟节点。
二、Merge的使用
(1)activity中的onCreate方法中的setContentView(R.layout.main2);
(2)应用Include或者ViewStub标签从外部导入xml结构时,可以将被导入的xml用merge作为根节点表示,这样当被嵌入父级结构中后可以很好的将它所包含的子集融合到父级结构中,而不会出现冗余的节点。<include layout="@layout/main2"/>
(3)当需要扩充的xml layout本身是由merge作为根节点的话,需要将被导入的xml layout置于 viewGroup中,同时需要设置attachToRoot为True。
View view = inflater.inflate(R.layout.main2, container, true);
三、Merge的注意事项
(1)<merge />只可以作为xml layout的根节点。
(2)如果你所创建的xml layout并不是用FramLayout作为根节点(而是应用LinerLayout等定义root标签),就不能应用上边的例子通过merge来优化UI结构。【merge的布局效果跟FrameLayout是等同的】
(3)Merge的父布局最好也是FrameLayout,因为使用Merge优化是指将<merge />内的元素添加到<merge />的父元素里,如果父布局不是FrameLayout,那么merge的元素添加到父布局中后,本来的展现效果就是发生变化——按照父布局的样式进行展现。
四、Merge的子布局的LayoutParams设置
可以暂时认为Merge就等同于Framelayout。如果merge节点里面有Linearlayout布局,那么如何在Java代码中通过LayoutParames来设置这个LinearLayout相对于父节点的位置呢?
(1)例如,布局文件如下:(LinearLayout使用的是自定义的)
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- viewPager:图片展现 --> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- 指示器圆点 --> <!-- android:layout_marginLeft="20dp" android:layout_marginStart="20dp" --> <com.why.screenslidepagerdemo.custom.PageIndicator android:id="@+id/indicator" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="20dp" android:gravity="bottom|center_horizontal" app:indicator_spacing="5dp" app:indicator_type="fraction" /> </merge>
(2)引用这个布局文件的代码:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_slidepager); }
(3)自定义LinearLayout中的相关代码
setLayoutParams 是设给父节点的,所以需要知道父节点的类型,在这里merge可以认为framelayout,但是还是需要通过下面的代码给这个自定义LinearLayout设置下LayoutParams
if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;//暂时不起作用 this.setLayoutParams(params); }
然后就可以正常使用了,使用代码如下:
FrameLayout.LayoutParams params1 = (FrameLayout.LayoutParams) this.getLayoutParams(); params1.bottomMargin = dp2px(getContext(), 5); this.setLayoutParams(params1);
大概代码如下:
public class PageIndicator extends LinearLayout //构造函数1 public PageIndicator(Context context) { this(context, null); } //构造函数2 public PageIndicator(Context context, AttributeSet attrs) { this(context, attrs, 0); } //构造函数3 public PageIndicator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //这里取得declare-styleable集合 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PageIndicator, 0, 0); try { //这里从集合里取出相对应的属性值,第二参数是如果使用者没用配置该属性时所用的默认值 mIndicatorSpacing = a.getDimensionPixelSize(R.styleable.PageIndicator_indicator_spacing,dp2px(context, DEFAULT_INDICATOR_SPACING)); int indicatorTypeValue = a.getInt(R.styleable.PageIndicator_indicator_type,mIndicatorType.type); mIndicatorType = IndicatorType.of(indicatorTypeValue); } finally { //关闭资源 a.recycle(); } init();//设置布局参数 } //设置布局参数 private void init() { //这个代码是有效的 this.setOrientation(HORIZONTAL);//设置水平展现 //this.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);//有效 Log.v("PageIndicator", (this.getLayoutParams() instanceof FrameLayout.LayoutParams) + "");//false Log.v("PageIndicator", (this.getLayoutParams() instanceof LinearLayout.LayoutParams) + "");//false /* * 1、通过merge来优化UI结构一般用于FrameLayout根节点 * 2、setLayoutParams 是设给父节点的。 */ //下面的代码是给这个layout设置layoutParams为FrameLayout.LayoutParams,便于后面getlayoutParams时可以指定类型 if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); params.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;//暂时不起作用 this.setLayoutParams(params); } //使用如下: /*FrameLayout.LayoutParams params1 = (FrameLayout.LayoutParams) this.getLayoutParams(); params1.bottomMargin = dp2px(getContext(), 5); this.setLayoutParams(params1);*/ }
参考资料: