Android自定义标题栏学习详解!

提起标题栏,相信大家都不会陌生,因为它太常见了。标题栏一般出现在整个程序的上面用作界面提示和返回等操作。一般是一种组合式自定义控件,下面让我们一起学习来学习一下标题栏的制作吧。

1.制作布局

标题栏其实是一种自定义控件,创建这种复合组件首先第一步就是创建一个包含多个组件的viewgroup,通常我们把一个TextView和两个Button放进去,这是一种比较经典的界面,你可能会说有的界面只有一个按钮啊,其实实现这种效果也不难,我们可以在程序中动态地设置界面的显示与隐藏嘛。那我们现在新建一个名字叫title.xml的布局文件:

"http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/tab_bg">
    

创建好这个复合组件的布局文件后,我们就可以在任意一个activity的布局文件中使用啦,使用的方法也很简单,比如我们想在MainActivity中使用,那就在MainActivity对应的布局activity_main.xml中引用一下就可以了:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/title"/>

LinearLayout>

看到没,其实就是一句话用include把它引用过来就可以了。让我们来看看效果吧:

Android自定义标题栏学习详解!_第1张图片

我就没有去网上为这两个Button去找好看的背景了哈,所以使用了原生的Button样式,我着重介绍下设计的方法。这样我们就把这包含三个控件的复合组件当做一个整体了,可以在任何布局中使用到它了。不过目前的话除了表面上有点自定义标题栏的样子,其实是完全没有实际使用效果,因为你点击上面的Button,没有任何应答。我们当然不会希望这个标题栏只能用来看的吧,那我们就下面动手为它设置监听器吧!

2.制作自定义控件

其实刚刚我们制作的布局文件如果直接被引入到每个activity中的话,那么在每个activity中我们都要先获取到控件实例,然后为控件注册监听器,然后重写监听方法…….比如上面的Back按钮其实在大多activity中都是销毁当前的活动,这个按钮的功能可能在所有的活动中都是相同的,要是直接通过引入布局的话,就要为每个按钮注册相同的监听事件了。可以预料到,通过这种方式写的代码冗余度会非常高,太多的重复代码。那有什么办法能够避免这么做,避免写太多重复代码?

这种情况下,我们可以通过自定义控件的方式来解决,自定义控件其实很简单,新建一个类继承ViewGroup,然后重写它的带有参数的构造方法,代码如下所示:

public class TitleLayout extends LinearLayout {
    private Button titleLeft;
    private Button titleRight;
    private TextView titleText;
    public TitleLayout(Context context, AttributeSet attributes){
        super(context,attributes);
        LayoutInflater.from(context).inflate(R.layout.title,this);
        titleLeft=(Button) findViewById(R.id.title_left);
        titleRight=(Button) findViewById(R.id.title_right);
        titleLeft.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                ((Activity)getContext()).finish();
            }
        });
        titleRight.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getContext(),"你点击了编辑",Toast.LENGTH_LONG).show();
            }
        });

    }
}

好了,这个自定义控件也完成了,接下来就是在布局文件中引用了,修改之前布局文件的代码:

"http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.leslie.topbar.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    com.example.leslie.topbar.TitleLayout>

可见,在布局文件中引入我们自定义的控件和一般控件不同的地方就是要提供完整的包名+类名。其他参数的设置是一样的。上面的TitleLayout我们重写了LinearLayout中带有两个参数的构造函数,第一个参数是上下文,一般就是我们要使用这个自定义控件的activity。第二个参数是属性集合,看到上面的引入com.example.leslie.topbar.TitleLayout指定了自定义控件的宽度和高度了吗?当然,你也可以指定更多的参数。这些在xml中定义的属性全部会传递给这个属性集合参数,供调用父类构造器时计算大小以及绘图。然后我们在构造函数中获取了LayoutInflater的实例,并用它去加载我们已经制作好了的布局文件R.layout.Title,inflate()函数的第二个参数是为所加载的布局文件提供父容器,这里我们指定为本自定义控件,就写this。接着我们在构造函数中获取了控件的实例,并分别为他们注册监听器。这样的话,我们就不需要在每个使用到这个控件的activity中重复去获取,添加监听器了。
让我们来看看效果吧:

Android自定义标题栏学习详解!_第2张图片

点击编辑会弹出提示,点击左边的退出,当前activity会销毁退出。具体工作中我们就可以添加具体的逻辑了。这样的话,我们每当在一个布局文件中引入TitleLayout,返回按钮和编辑按钮的点击事件都已经确定好了,就不劳我们再去帮它实现了。

3.高度化自定义控件

不知道你仔细想过没有,经过以上两步的自定义标题栏是有一定的局限性的。不同界面的标题栏虽然大致都有按键Button来供人们点击,都有TextView供大家显示界面提示信息。但是具体的界面Button,TextView显示的文字以及背景图案千差万别,就拿上面我们设计的标题栏右边的按钮来说吧,可能它在一个界面是编辑按钮,但是可能在另一个界面就是设置,添加,查找,等按钮,同时中间的TextView也不是一层不变的,在各种界面都要显示对应的标题。不同界面的按钮点击事件也不会相同,可能这个界面最右边是点击跳转到A界面,那个界面点击跳转到B界面。那遇到这些情况我们改怎么办呢?

很明显,我们不可以在TitleLayout的构造函数中对每个Button,TextView重新设置他们的文字,背景,以及点击事件。最好是把这些属性,点击事件开放出来让引用我们自定义控件的activity自己去根据自己程序的需要设置相应的参数以及具体的实现。那有没有办法可以制作出这种低耦合度的自定义控件呢?答案是肯定的。

1.首先为标题栏自定义属性:
自定义属性的目的是方便activity在引用我们的标题栏的时候直接指定我们想要修改的属性值,为一个View提供可自定义的属性非常简单,只需要在res资源目录的values目录下创建一个attrs.xml的属性定义文件就可以了。代码如下所示:


<resources>
    <declare-styleable name="TopBar">
        <attr name="title" format="string"/>
        <attr name="rightButtonBackground" format="reference|color"/>
        <attr name="rightButtonText" format="string"/>
    declare-styleable>
resources>

我们用declare-styleable标签申明了自定义属性,名字就叫做TopBar。那这个TopBar的自定义属性当然是用来定义我们的标题栏的一些属性,你可以在这里定义标题栏ViewGroup以及包括里面各个View的各种属性:字体、字体大小、字体颜色、背景、边距、文字内容….等各种属性,只要遵循属性定义规则就好了。这里为了简单理解,我们就定义三个属性吧,分别是:标题栏的标题,右边按钮上的文字内容,右边按钮的背景。
从上面可以看出,定义一条属性是以attr标签开头,name表示这条属性的名字(你可以自己取容易理解的),format指定属性值的类型(string,color,dimension,reference…)。

2.引用标题栏的时候指定属性
我们的标题栏现在是一个UI模板,还记得我们怎么使用它吗?没错,在需要使用的地方直接引用就可以了,还记得以下代码吗?


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.leslie.topbar.TitleLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:custom="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:title="标题一"
        custom:rightButtonText="设置"
        >
    com.example.leslie.topbar.TitleLayout>

LinearLayout>

怎么样?这不就是我们在环节2中引用用的方法吗?略有不同的地方就是多了几个内容:首先可以看到包名+类名下面有两句字符串:

xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"

这一段代码就是在指定引用的名字空间xmlns,即xml namespace。这里指定了命名空间为android和custom,因此在接下来使用系统的属性和自定义的属性的时候,才可以通过android:和custom:来分别引用android系统的属性和自己定义的属性。第三方的控件都需要使用第二句代码来引入命名空间。可以看出,我们在我们的activity中引入标题栏的时候指定了两个属性,一个是标题内容为“标题一”,按钮上的文字为“设置”。

3.修改TitleLayout代码

目前我们已经定义了属性,并且在引用布局的时候也指定了属性,接下来就要在TitleLayout的构造函数中修改下代码了:

public class TitleLayout extends LinearLayout {

    private Button titleLeft;

    private Button titleRight;

    private TextView titleText;

    private String mTitle;

    private String mRightButtonText;

    private Drawable mRightButtonBackground;

    public TitleLayout(Context context, AttributeSet attributes){

        super(context,attributes);

        //获取在XML中自定义的那些属性
        TypedArray typedArray=context.obtainStyledAttributes(attributes,R.styleable.TopBar);
        mTitle=typedArray.getString(R.styleable.TopBar_title);
        mRightButtonText=typedArray.getString(R.styleable.TopBar_rightButtonText);
        mRightButtonBackground=typedArray.getDrawable(R.styleable.TopBar_rightButtonBackground);
        //获取完属性后要进行资源回收
        typedArray.recycle();

        //加载布局文件
        LayoutInflater.from(context).inflate(R.layout.title,this);
        //获取各个控件实例
        titleLeft=(Button) findViewById(R.id.title_left);
        titleRight=(Button) findViewById(R.id.title_right);
        titleText=(TextView)findViewById(R.id.title_text);
        //设置各个自定义属性
        if (mTitle != null) {
            titleText.setText(mTitle);
        }
        if (mRightButtonText!=null) {
            titleRight.setText(mRightButtonText);
        }
        if (mRightButtonBackground!=null){
        titleRight.setBackgroundDrawable(mRightButtonBackground);
        }

        //注册点击事件
        titleLeft.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                ((Activity)getContext()).finish();
            }
        });
        titleRight.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getContext(),"你点击了编辑",Toast.LENGTH_LONG).show();
            }
        });

    }
}

代码有点长,下面我们一步一步分析:
首先我们还是重写了带有两个参数的构造函数,这两个参数我上面已经讲过,其中第二个参数就是我们引用TitleLayout时指定的那些android:xxx和custom:xxx参数的集合。我们获取xml布局文件中自定义的属性是通过:

TypedArray typedArray=context.obtainStyledAttributes(attributes,R.styleable.TopBar);

来获取一个TypedArray这样的一个类型数组,接下来通过TypedArray的对象的getString(),getColor(),getDemension(),getDrawable()等一系列方法获取到布局文件中具体指定的值。如果没有在布局文件中指定自定义属性的值话去用getxxx方法获取到的值为null,所以接下来我们有一个判空操作,需要提醒的是,把所有的属性值获取完毕后,要调用TypedArray的recyle方法来进行资源回收。然后我们获取了LayoutInflater的实例去加载布局文件,并且我们获取到三个子控件的实例,这样我们就能修改他们的文字内容,字体颜色,字体大小,背景图片等等属性了。
Android自定义标题栏学习详解!_第3张图片

看到了吗这个标题栏和以前的不一样吧?它的标题内容,以及按钮的内容都是我们在引用它的时候直接给它指定的!所以你可以按照上面的方法定义更多的自定义属性,只要在引入它的布局中指定具体的值就可以了。是不是感觉方便多了?尤其是activity越来越多,每个activity的标题栏都有个性化差异的时候就可以用这种方式去实现。

4.继续进阶,创造属于每个activity自己的点击事件

经过以上几步,我们实现的标题栏较大程度的个性定制,我们现在已经可以通过定义许多自定义属性,在布局引用的时候静态地指定具体值就可以让每个activity中的标题栏有个性化的差异。不过好学的你肯定会继续问,现在属性我知道设置了,可是每个引入标题栏的activity中那几个按钮的点击事件总是一成不变地显示相同的逻辑,能不能让不同的activity点击各自个按钮产生不同的效果呢?答案当然也是肯定的。废话不多说,让咱们开始吧:

这里我们让相同的按钮的点击事件在不同的activity实现的功能不同,因此我们不能直接在我们的TitleLayout这个标题栏实现类中添加具体的实现逻辑,只能通过接口回调,把具体的实现逻辑交给调用者(就是引用它的activity),实现过程如下:

public class TitleLayout extends LinearLayout {

    private Button titleLeft;

    private Button titleRight;

    private TextView titleText;

    private String mTitle;

    private String mRightButtonText;

    private Drawable mRightButtonBackground;

    private topBarClickListener mListener;

    public TitleLayout(Context context, AttributeSet attributes){

        super(context,attributes);

        //获取在XML中自定义的那些属性
        TypedArray typedArray=context.obtainStyledAttributes(attributes,R.styleable.TopBar);
        mTitle=typedArray.getString(R.styleable.TopBar_title);
        mRightButtonText=typedArray.getString(R.styleable.TopBar_rightButtonText);
        mRightButtonBackground=typedArray.getDrawable(R.styleable.TopBar_rightButtonBackground);
        //获取完属性后要进行资源回收
        typedArray.recycle();

        //加载布局文件
        LayoutInflater.from(context).inflate(R.layout.title,this);
        //获取各个控件实例
        titleLeft=(Button) findViewById(R.id.title_left);
        titleRight=(Button) findViewById(R.id.title_right);
        titleText=(TextView)findViewById(R.id.title_text);
        //设置各个自定义属性
        if (mTitle != null) {
            titleText.setText(mTitle);
        }
        if (mRightButtonText!=null) {
            titleRight.setText(mRightButtonText);
        }
        if (mRightButtonBackground!=null){
        titleRight.setBackgroundDrawable(mRightButtonBackground);
        }

        //注册点击事件
        titleLeft.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {

                mListener.leftClick();
            }
        });
        titleRight.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.rightClick();
            }
        });

    }

    //定义一个接口,具体实现由调用者自己添加
    public interface topBarClickListener{
        void leftClick();
        void rightClick();
    }

    //暴露一个方法给调用者来注册接口回调
    public void setOnTopBarClickListener(topBarClickListener listener){
        mListener=listener;
    }
}

首先就是修改我们的标题栏的实现类TitleLayout啦,这里我们添加了一个接口:

 public interface topBarClickListener{
        void leftClick();
        void rightClick();
    }

接口中只有两个抽象方法,对应两个按钮的点击事件。我们在这里不需要考虑这两个方法具体的逻辑,只需要暴露一个方法给回调者来注册一下接口回调就可以了:

public void setOnTopBarClickListener(topBarClickListener listener){
        mListener=listener;
    }

外面调用者在获取titlelayout实例后,需要提供一个接口的实现类来完成注册,这个实现类对象就会传递给TitleLayout的接口成员变量mListener,同时我们在具体的Button的点击事件中调用接口的方法完成处理,具体的逻辑由mListener所引用对象来实现。其他的代码没什么改变。下面我们创建两个activity,分别是MainActivity和SecondActivity。代码很简单:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.activity_main);
        TitleLayout topbar=(TitleLayout) findViewById(R.id.main_topbar);
        topbar.setOnTopBarClickListener(new TitleLayout.topBarClickListener() {
            @Override
            public void leftClick() {
                finish();
            }

            @Override
            public void rightClick() {
                Intent intent=new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);

            }
        });
    }
}

mainactivity首先获取了这个标题栏的实例,接着需要实现它的topBarClickListener接口,并完成接口中的方法的具体实现,这里每个activity中rightClick()和leftClick()方法的具体实现逻辑可以不同,这里我们让左边按钮的点击事件响应为退出当前activity,右边的按钮点击事件响应为跳转到指定的activity中。同样的是SecondActivity中的代码:

public class SecondActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstancedState){
        super.onCreate(savedInstancedState);
        getSupportActionBar().hide();
        setContentView(R.layout.secondactivity);
        TitleLayout topbar=(TitleLayout) findViewById(R.id.second_topbar);
        topbar.setOnTopBarClickListener(new TitleLayout.topBarClickListener() {
            @Override
            public void leftClick() {
                finish();
            }

            @Override
            public void rightClick() {
                Toast.makeText(SecondActivity.this,"你点击了查找",Toast.LENGTH_LONG).show();

            }
        });
    }
}

和MainActivity中的代码大致一样,不同的时点击第二个按钮不再跳转到指定的activity中,而是显示一段提示文字。
运行一下如下所示:
Android自定义标题栏学习详解!_第4张图片
点击一下设置按钮就会进入第二个按钮:
Android自定义标题栏学习详解!_第5张图片
点击一下查找就会提示你点击了查找。

好了,通过以上方法,你还能实现更为复杂的逻辑功能,这个就留给大家吧,我们今天就到这里,自定义标题栏的学习暂时就告一段落了,需要源代码的朋友可以点击源代码进行下载

你可能感兴趣的:(Android自定义标题栏学习详解!)