用Android studio 2.3调度程序时,提示 “Installation failed with message Failed to establish session”错误,
需要在在开发者选项里 关闭MIUI优化!
用Android studio 2.3调度程序时,提示 “Installation failed with message Failed to establish session”错误,
需要在在开发者选项里 关闭MIUI优化!
res/drawable/shape_test.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line" >
<stroke
android:dashGap="3dp"
android:dashWidth="6dp"
android:width="1dp"
android:color="@android:color/black" />
<!-- 虚线的高度 -->
<size android:height="1dp" />
</shape>
注意:如果在android 6.0上没有效果可以在代码中写
line.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
或者在布局文件中加入
android:layerType="software"
效果:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="5dp"
android:layout_gravity="center_vertical"
android:background="@drawable/shape_test" />
</FrameLayout>
res/drawable/shape_test.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="-40%"
android:pivotY="87%">
<shape android:shape="rectangle">
<solid android:color="@android:color/black" />
<stroke
android:width="0.5dp"
android:color="@android:color/black" />
</shape>
</rotate>
</item>
</layer-list>
效果:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:background="@drawable/shape_test" />
</FrameLayout>
res/drawable/shape_test.xml文件:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:useLevel="false" >
<solid android:color="@android:color/transparent" />
<stroke
android:dashGap="2dp"
android:dashWidth="2dp"
android:width="0.5dp"
android:color="@android:color/black" />
</shape>
效果:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:background="@drawable/shape_test" />
</FrameLayout>
res/drawable/shape_test.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 实心 -->
<solid android:color="@android:color/transparent" />
<!-- 渐变 -->
<gradient
android:angle="90"
android:endColor="@android:color/black"
android:startColor="@android:color/white" />
<!-- 描边 -->
<stroke
android:width="1dp"
android:color="@android:color/black" />
<corners android:radius="5dp" />
<!--可以设置边距
<padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
-->
</shape>
效果:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:background="@drawable/shape_test" />
</FrameLayout>
res/color/color_test.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:color="@android:color/holo_blue_bright"
android:state_focused="true"
android:state_pressed="true"
/>
<item
android:color="@android:color/holo_blue_bright"
android:state_focused="false"
android:state_pressed="true"
/>
<item
android:color="@android:color/holo_blue_bright"
android:state_focused="true"
/>
<item
android:color="@android:color/holo_blue_dark"
android:state_focused="false"
android:state_pressed="false"
/>
</selector>
效果:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击变色"
android:layout_gravity="center"
android:textColor="@color/color_test" />
</FrameLayout>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@mipmap/loading_01"
android:duration="200" />
<item
android:drawable="@mipmap/loading_02"
android:duration="200" />
<item
android:drawable="@mipmap/loading_03"
android:duration="200" />
<item
android:drawable="@mipmap/loading_04"
android:duration="200" />
<item
android:drawable="@mipmap/loading_05"
android:duration="200" />
<item
android:drawable="@mipmap/loading_06"
android:duration="200" />
<item
android:drawable="@mipmap/loading_07"
android:duration="200" />
<item
android:drawable="@mipmap/loading_08"
android:duration="200" />
<item
android:drawable="@mipmap/loading_09"
android:duration="200" />
<item
android:drawable="@mipmap/loading_10"
android:duration="200" />
<item
android:drawable="@mipmap/loading_11"
android:duration="200" />
<item
android:drawable="@mipmap/loading_12"
android:duration="200" />
</animation-list>
在代码中写入
AnimationDrawable an = (AnimationDrawable) loding.getBackground();
an.start();
在平时设计UI时,相信大多数人用得比较多的布局就是LinearLayout和RelativeLayout了,毕竟这两种布局能实现我们一般的需求。还记得刚开始学Android时,使用Eclipse进行开发,每次新建一个布局文件,该布局文件默认使用的布局是LinearLayout。后来改用Android Studio开发,SDK版本更新之后,每次新建的布局文件改成用RelativeLayout作为默认布局了。 很多人说这个Google团队出于性能考虑而进行的优化,的确,RelativeLayout能减少布局嵌套从而提高布局的性能,而使用Linearlayout如果不注意的话可能出现布局嵌套层次太深,从而大大降低了布局的性能。但是LinearLayout真的比不上RelativeLayout吗?答案是否定的。
View的绘制一般都是经过onMeasure,onLayout,和onDraw,LinearLayout和RelativeLayout都是继承自ViewGroup,而ViewGroup又是继承View的,所以LinearLayout和RelativeLayout一般也会经历上面所说的三个流程。但是在ViewGroup一般默认不调用onDraw,因为ViewGroup是容器,一般只用来装载控件,所以负责测量控件总共所需的宽高,并将控件放在我们指定的位置就行了。所以接下来我打算只从onMeasure和onLayout两个方面对比LinearLayout和RelativeLayout。
先看看LinearLayout的onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
我们都知道LinearLayout有两个方向走向,一个是竖直方向,一个是水平方向。可以看出Linearlayout在测量宽高时是根据我们设置的方向分别调用不同的测量方法,这两个测量方法大致一样,我们以measureVertical来说明LinearLayout的具体测量过程吧。因为measureVertical这个方法有点长,所以我们贴部分代码说明问题就行了。
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
在measureVertical方法里面调用了这个方法,什么意思呢?我们都知道在LinearLayout中,我们可以使用layout_weight属性,当子View使用了layout_weight属性时就会调用这个方法,这个方法的大概步骤是先不限制设置了weight属性的子View的大小,只要容器剩余的空间还足够就不对该子View作处理,但凡事都有个度,所以不可能每个设置了weight属性的子View都无限大吧,所以当所有子View测量完毕后,会对设置了weight属性的子View再调用一次measure方法,如下:
// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight, MeasureSpec.EXACTLY));
}
}
}
接下来说说RelativeLayout的onMeasure方法,部分源码如下:
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
int[] rules = params.getRules(layoutDirection);
applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
for (int i = 0; i < count; i++) {
final View child = views[i];
if (child.getVisibility() != GONE) {
final LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
if (isLayoutRtl()) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, myWidth - params.mLeft);
} else {
width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
}
} else {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
width = Math.max(width, params.mRight);
} else {
width = Math.max(width, params.mRight + params.rightMargin);
}
}
}
if (isWrapContentHeight) {
if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
height = Math.max(height, params.mBottom);
} else {
height = Math.max(height, params.mBottom + params.bottomMargin);
}
}
if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}
if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
由上面源码我们可知RelativeLayout对子View分别进行了竖直和水平方向的两次测量,这不难理解,因为RelativeLayout里面每个子View的位置都是相对的,所以要分别从水平和竖直两个方向对子View进行测量。
接下来在来看看LinearLayout和RelativeLayout的onLayout方法。同样的,先看LinearLayout的onLayout方法,如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
简单分析这个方法,即先根据LinearLayout设置的方向调用不同的方法,和上面的onMeasure方法中调用的情况类似,在这两个方法做的事情也差不多,就是将子View逐个按照其大小和布局参数摆放在对应的位置。
再看看RelativeLayout中onLayout方法,如下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
RelativeLayout的onLayout方法和LinearLayout的onLayout方法类似,都是遍历所有的子View,逐个逐个地将子View放在对应的位置,所以两者的onLayout方法性能相差不大。
总结:LinearLayout和RelativeLayout的性能差别主要体在onMeasure方法上,RelativeLayout始终要从竖直和水平两个方向对子View进行测量,而Linearlayout,当我们没有在子View中使用layout_weight属性时,LinearLayout只需对子View进行一次测量,反则需要对子View进行两次测量以确定最终大小,所以如果可以我们尽量少用layout_weight属性。在使用这两个布局之前,我们可以先进行衡量,如果需要实现的布局嵌套层次不深或者嵌套层次已经固定了,可以考虑用LinearLayout,相对的,如果某个布局嵌套层次很深,此时应该考虑使用RelativeLayout来减少嵌套层析从而优化布局的性能。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
表示将resource指定的布局添加到root中,添加的过程中resource所指定的的布局的根节点的各个属性都是有效的
Activity布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/ll"
tools:context="org.sang.layoutinflater.MainActivity">
</LinearLayout>
linearlayout.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"
android:gravity="center"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
java代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.linearlayout, ll,true);
}
这里我们都没写将inflate出来的View添加到ll中的代码,但是linearlayout布局文件就已经添加进来了。
这就是因为我第三个参数设置为了true,表示将第一个参数所指定的布局添加到第二个参数的View中。
如果root不为null,而attachToRoot为false的话,表示不将第一个参数所指定的View添加到root中。
那么这个时候有的小伙伴可能就有疑问了,既然不添加到root中,那我还写这么多干嘛?第二个参数直接给null不就可以了?
其实不然,这里涉及到另外一个问题:我们在开发的过程中给控件所指定的layout_width和layout_height到底是什么意思?该属性的表示一个控件在容器中的大小,就是说这个控件必须在容器中,这个属性才有意义,否则无意义。
这就意味着如果我直接将linearlayout加载进来而不给它指定一个父布局,则inflate布局的根节点的layout_width和layout_height属性将会失效(因为这个时候linearlayout将不处于任何容器中,那么它的根节点的宽高自然会失效)。
如果我想让linearlayout的根节点有效,又不想让其处于某一个容器中,那我就可以设置root不为null,而attachToRoot为false。
这样,指定root的目的也就很明确了,即root会协助linearlayout的根节点生成布局参数,只有这一个作用。
OK,还是上面的布局文件,如果我想将之添加到activity的布局中又该如何呢?
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.linearlayout, ll, false);
ll.addView(view);
}
静态设置
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.shijiusui.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/right_fragment"
android:name="com.shijiusui.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
动态添加
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.shijiusui.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/right_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.commit();
}
动态添加Fragment步骤:
返回栈
private void replaceFragment(Fragment fragment){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
transaction.addToBackStack(null);//----------->
transaction.commit();
}
这里我们在事务提交之前调用了FragmentTransaction的addToBackStack()方法,它可以接收一个名字用于描述返回栈的状态,一般传入null即可。
Fragment和Activity之间进行通信
为了方便Fragment和Activity之间通信,FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取碎片的实例。
RightFragment rightFragment = (RightFragment) getFragmentManager()
.findFragmentById(R.id.right_fragment);
在Fragment中可以通过调用getActivity()方法来得到和当前Fragment相关联的Activity实例。
MainActivity activity = (MainActivity)getActivity();
生命周期
onAttach(Context)
onCreate(Bundle)
onCreateView(LayoutInflater, ViewGroup, Bundle)<——-返回栈
onActivityCreated(Bundle)
onStart()
onResume()
onPause()
onStop()
onDestroyView()—–>返回栈
onDestroy()
onDetach()
RightFragment第一次显示时,依次执行
onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()
当被其他替换时,进入停止状态,执行的是
onPause()->onStop()->onDestroyView()
如果在替换的时候没有调用addToBackStack()方法,此时会进入销毁状态,会接着执行
—->onDestroy()->onDetach()
当在替换的时候调用了addToBackStack()方法,按返回键会回退到RightFragment,由于RightFragment重新回到了运行状态,因此会执行
onActivityCreated()->onStart()->onResume()
注意,此时onCreate()和onCreateView()方法并不会执行,因为我们借助了addToBackStack()方法使得RightFragment和它的视图并没有销毁。
还有一个方法onSaveInstanceState()方法可以保存数据。在onCreate()、onCreateView()和onActivityCreated()中可以获取保存的数据。
compile 'com.android.support:recyclerview-v7:24.2.1'
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
class XXXViewHolder extends RecyclerView.ViewHolder{
public XXXViewHolder(View view){
super(view);
}
}
public class XXXAdapter extends RecyclerView.Adapter<XXXViewHolder>{
@Override
public XXXViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View view = LayoutInflater.from(parent.getContext()).inflate(..., false);
XXXViewHolder holder = new XXXViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(XXXViewHolder holder, int position){
}
@Override
public int getItemCount(){
return ...;
}
}
RecyclerView recyclerView = findViewById(...);
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
XXXAdapter adapter = new XXXAdapter(...);
recycler.setAdapter(adapter);
横向滚动效果
RecyclerView recyclerView = findViewById(...);
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);//------------>
recyclerView.setLayoutManager(layoutManager);
XXXAdapter adapter = new XXXAdapter(...);
recycler.setAdapter(adapter);
RecyclerView还提供了GridLayoutManager和StaggeredGridLayoutManager这两种内置的布局排列方法。
GridLayoutManager可以用于实现网格布局。
StaggeredGridLayoutManager可以用于实现瀑布流布局。
RecyclerView recyclerView = findViewById(...);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
XXXAdapter adapter = new XXXAdapter(...);
recycler.setAdapter(adapter);
RecyclerView的点击事件
不同于ListView的是,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,而是需要我们自己给子项具体的View去注册点击事件。
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null){
actionBar.hide();
}
}
}
由于LinearLayout本身已经支持按比例指定控件的大小了,因此百分比布局只为FrameLayout和RelativeLayout进行功能扩展,提供了PercentFrameLayout和PercentRelativeLayout这两个全新的布局。
compile 'com.android.support:percent:24.2.1'
<android.support.percent.PercentFrameLayout
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">
<Button
android:id="@+id/button1"
android:text="Button1"
android:layout_gravity="left|top"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"
/>
<Button
android:id="@+id/button2"
android:text="Button2"
android:layout_gravity="right|top"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"
/>
<Button
android:id="@+id/button3"
android:text="Button3"
android:layout_gravity="left|bottom"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"
/>
<Button
android:id="@+id/button4"
android:text="Button4"
android:layout_gravity="right|bottom"
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"
/>
</android.support.percent.PercentFrameLayout>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
默认是一个圆形旋转进度条。
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:max="100"
/>
水平进度条
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Button" />
我们在布局文件里面设置的文字是“Button”,但最终的显示结果却是“BUTTON”。这是由于系统会对Button中的所有英文字母自动进行大写转换,如果你不想要的效果,可以使用如下配置来禁用这一默认特性:
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textAllCaps="false"
android:text="Button" />