Android开发之自定义组件和接口回调

说到自定义控件不得不提的就是接口回调,在Android开发中接口回调用的还是蛮多的。在这篇博客开始的时候呢,我想聊一下iOS的自定义控件。在iOS中自定义控件的思路是继承自UIView, 在UIView的子类中组合一些控件,对外暴漏一些属性和回调接口,并留有必要的实现方法。在iOS自定义控件中常用的回调有两种,一是委托代理回调(Delegate),另一种是Block回调。如果你想对这两者有所了解,请参考我之前的博客《Objective-C中的委托(代理)模式》、《Objective-C中的Block回调模式》、《设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)》。

在Android自定义控件时用到的接口回调和iOS开发中使用到的Delegate回调以及Block回调即为相似,就连实现方式都大同小异。今天的内容就自定义一个Android控件,并且以此控件为基础,聊一下Android中的接口回调(确切的说应该是Java语言中的接口回调)。废话少说,进入今天的主题。

一.自定义控件的UI实现

上面有提到,iOS开发中,自定义控件一般式继承自UIView的,然后再UIView的子类中做一些事情。而Android开发中的自定义控件也是继承自View, 但是今天我们的自定义控件是继承自FrameLayout, 在此基础上我们自定义一些东西。因为FrameLayout, LinearLayout等布局方式都是继承自ViewGroup的,而ViewGroup则继承自View, 所以在自定义控件时,继承自FrameLayout等布局方式肯定是可以的。

1. 实现效果分析

接下来我们要自定义一个导航栏,而这个导航栏是模仿iOS系统中的NavigationBar。因为Android开发中没有这个控件,所以我们需要自定义这个控件供开发者使用。下方是我们要实现的效果。上方的导航栏是我们自定义的NavigationBar,和iOS系统的导航栏类似。点击左边的返回按钮,会退出当前Activity。点击右边的借口回调测试,会通过接口回调的形式来在当前Activity中显示Toast提示。在调用该组件时,可以知道中间的Title.

2. UI布局分析以及Xml布局文件实现

接下来我们将会对UI进行拆分,详细的看一下上面的NavigationBar是由哪些基础控件组成的。分析完毕后,在通过一些布局方式将这些基础控件进行组合,拼装,最终成为我们想要使用的自定义控件。下方是手动画的上述自定义控件UI原理图,如下所示。

最下边的布局我们采用的时FrameLayout方式,并设置其背景颜色。返回图标(ImageView)和 返回文字(TextView)放在了一个水平布局的LinearLayout上。这两者上面放了一个透明的Button, 用来实现返回操作。中间的Title(TextView) 在FrameLayout中设置成居中显示即可。Call Back是一个Button, 用来测试下面的接口回调。

上面的布局格式具体落实到xml中,如下所示,具体思想就是上方叙述的东西。该自定义控件布局文件如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content"
 5     android:background="#cccccc">
 6     <LinearLayout
 7         android:orientation="horizontal"
 8         android:layout_width="match_parent"
 9         android:layout_height="20pt"
10         android:background="@drawable/background">
11         <ImageView
12             android:id="@+id/back_title"
13             android:layout_width="wrap_content"
14             android:layout_height="18pt"
15             android:layout_gravity="center"
16             android:src="@drawable/back"/>
17         <TextView
18             android:layout_width="wrap_content"
19             android:layout_height="wrap_content"
20             android:layout_gravity="center"
21             android:textSize="8pt"
22             android:text="返回"/>
23     </LinearLayout>
24 
25     <Button
26         android:id="@+id/back_button"
27         android:layout_width="50pt"
28         android:layout_height="20pt"
29         android:layout_gravity="center_vertical"
30         android:alpha="0" />
31 
32     <TextView
33         android:id="@+id/navigation_title"
34         android:layout_width="wrap_content"
35         android:layout_height="wrap_content"
36         android:textSize="10pt"
37         android:layout_gravity="center"
38         android:text="标题"/>
39 
40     <Button
41         android:id="@+id/call_back"
42         android:layout_width="wrap_content"
43         android:layout_height="wrap_content"
44         android:layout_gravity="right"
45         android:text="接口回调测试"/>
46 </FrameLayout>

二. 为UI绑定事件以及留出回调接口

UI实现好后,就说明我们自定义组件的壳儿已经做好了,但是其内在的东西还需要实现。也就是说需要为上述实现的UI绑定Java类,并在类中处理控件的一些响应事件,以及在类中留出必要的接口来改变自定义组件的属性。接下来来实现xml对应的Java类。

因为上述布局中,最外层我们使用的是FrameLayout布局,上面已经粗略的提过,我们可以继承自FrameLayout来做一些东西,因为FrameLayout的父类是View, 所以我们可以在此基础上做一些东西。同理,如果上述布局是使用其他布局来实现的,那么你就可以继承自其他布局的类来做一些东西。在本篇博客中我们就以FrameLayout为父类来实现我们自定义组件的关联类。

1. 继承FrameLayout并实现相应的构造函数,下方是我们要实现的构造函数。在构造函数中,我们需要与上述我们实现的xml布局文件进行关联,当然,我们使用的是LayoutInflater来实现的,自定义组件的构造函数如下所示。

    /*
     *自定义组件的构造方法
     */
    public CustomNavigationBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.custom_navigation, this);     //加载布局文件
    }

2. 实现好相应的构造方法并关联好相应的布局文件后,我们需要对布局文件中的控件进行事件的处理。下方的代码就是点击返回按钮要做的事情,因为点击返回按钮要做的事情就是结束当前Activity,所以不需要给调用者留有回调接口,在自定义组件的内部处理即可。下方代码就是获取UI中返回按钮,并处理返回事件的方法。下方的方法需要在构造函数中调用才会起作用,函数不调用怎么执行呢,对吧~。 下方代码较为简单,就是结束当前显示的Activity,处理返回按钮的事件如下:

 1     /*
 2      * 点击返回按钮方法
 3      */
 4     private void onClickBackButton() {
 5         Button button = (Button) findViewById(R.id.back_button);
 6         button.setOnClickListener(new OnClickListener() {
 7             @Override
 8             public void onClick(View v) {
 9                 ((Activity) getContext()).finish();
10             }
11         });
12     }

3.处理好返回事件后,我们需要做的还有就是为标题栏的标题留出设置的方法。也就是说在调用该自定义组件时,我们要能设置该组件的标题。要满足这一点,我们就需要在自定义组件中留出Title的setter方法了,并且这个Setter方法的访问权限必须是Public的,不然在外界就没办法访问这个方法了。下方就是这个设置title的Public方法。其实下方的代码还是比较简单的,就是通过ID来获取标题的TextView,并设置相应的title即可,代码如下:

 1     public String navigationTitle = "标题栏";
 2 
 3     /*
 4      * 设置标题栏的标题
 5      */
 6     public void setNavigationTitle(String navigationTitle) {
 7         this.navigationTitle = navigationTitle;
 8         TextView textView = (TextView) findViewById(R.id.navigation_title);
 9         textView.setText(navigationTitle);
10     }

4. 上面如果还算简单的话,下方就是自定义控件中稍稍有点难度的地方了。接下来我们要实现相应按钮的接口回调,在实现之前我们介绍一下为什么要实现接口的回调。因为有时候点击自定义控件中的按钮时,所做的事情在自定义控件的内部无法独立完成,需要在调用者中进行事件的处理,在这种情况下,我们就可以使用接口回调来处理。

上面实现的返回事件的处理就没必要使用接口的回调了,因为在自定义组件内部完全可以该功能。举个使用接口回调的栗子:比如点击自定义控件中某个按钮时,我们需要跳转到其他Activity,而这个Activity在我们实现自定义控件时是未知的,这时候就要用到我们的接口回调来实现了。在iOS开发中,同样遇到上述问题,所以iOS开发中也有各种回调比如Block回调,Delegate回调,Target-Action回调等都是iOS开发中常用的回调。虽然实现形式不同,但是其作用和Java中的接口回调是极为相似的。好,说这么多,接下来我们要为XML布局文件中id为call_back的按钮的点击事件通过接口回调的形式传递到调用者中。

(1)第一步我们要先实现接口回调的接口,这也是必须的,因为接口回调如果没有接口怎么能行呢。该接口是Public类型的,不然在调用者中是无法使用的。我们接口的名字为onClickCallBackListener, 在其中有一个方法,该方法是接口回调时要执行的方法。

1     /*
2      *创建回调接口
3      */
4     public static interface OnClickCallBackListener {
5         public void OnClickButton(View v);
6     }

(2) 声明一个私有的接口对象,并为这个私有的对象实现setter方法,该私有的接口对象是用来接受自定义组件调用者传过来的回调方法的。代码比较简单,在此就不做过多赘述了。

1     private OnClickCallBackListener callBackListener;            //声明接口对象
2     public void setCallBackListener(OnClickCallBackListener callBackListener) {
3         this.callBackListener = callBackListener;
4     }

(3) 实现好接口以及接收回调对象的变量后,接下来要做的事情就是获取自定义组件中相应按钮点击的事件,并在此按钮点击事件中执行传过来的接口对象相应的回调方法。下方这个方法,要在构造函数中调用。该方法的功能就是获取自定义组件的相应按钮的点击事件并执行接口对象的回调方法。具体实现如下:

    /*
     *点击按钮时执行接口回调
     */
    private void callBackButton() {
        Button button = (Button) findViewById(R.id.call_back);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (callBackListener != null) {
                    callBackListener.OnClickButton(v);
                }
            }
        });
    }

上方是把代码拆开来讲的,下方是整个自定义组件的实现类。具体代码如下所示。

 1 package com.example.lizelu.customnavigationbar;
 2 
 3 import android.annotation.TargetApi;
 4 import android.app.Activity;
 5 import android.content.Context;
 6 import android.os.Build;
 7 import android.util.AttributeSet;
 8 import android.view.LayoutInflater;
 9 import android.view.View;
10 import android.widget.Button;
11 import android.widget.FrameLayout;
12 import android.widget.TextView;
13 
14 /**
15  * Created by lizelu on 15/11/29.
16  */
17 public class CustomNavigationBar extends FrameLayout {
18 
19     /*
20      *创建回调接口
21      */
22     public static interface OnClickCallBackListener {
23         public void OnClickButton(View v);
24     }
25 
26 
27     private OnClickCallBackListener callBackListener;            //声明接口对象
28 
29     public String navigationTitle = "标题栏";
30 
31     /*
32      * 设置标题栏的标题
33      */
34     public void setNavigationTitle(String navigationTitle) {
35         this.navigationTitle = navigationTitle;
36         TextView textView = (TextView) findViewById(R.id.navigation_title);
37         textView.setText(navigationTitle);
38     }
39 
40     public void setCallBackListener(OnClickCallBackListener callBackListener) {
41         this.callBackListener = callBackListener;
42     }
43 
44     /*
45      *自定义组件的构造方法
46      */
47     public CustomNavigationBar(Context context, AttributeSet attrs) {
48         super(context, attrs);
49         LayoutInflater.from(context).inflate(R.layout.custom_navigation, this);     //加载布局文件
50         onClickBackButton();
51         callBackButton();
52     }
53     /*
54      * 点击返回按钮方法
55      */
56     private void onClickBackButton() {
57         Button button = (Button) findViewById(R.id.back_button);
58         button.setOnClickListener(new OnClickListener() {
59             @Override
60             public void onClick(View v) {
61                 ((Activity) getContext()).finish();
62             }
63         });
64     }
65 
66     /*
67      *点击按钮时执行接口回调
68      */
69     private void callBackButton() {
70         Button button = (Button) findViewById(R.id.call_back);
71         button.setOnClickListener(new OnClickListener() {
72             @Override
73             public void onClick(View v) {
74                 if (callBackListener != null) {
75                     callBackListener.OnClickButton(v);
76                 }
77             }
78         });
79     }
80 }

三.该自定义组件的调用方式

经过上面的过程,我们自定控件以及实现好了,接下来就是如何使用了。其实自定义组件的使用方式和系统自带的组件使用起来区别不大,没有什么特别之处。下方就让我们在Activity中使用上述我们自定义的控件吧。

1.首先在我们要使用该组件的Activity所对应的布局文件中加载我们的自定义组件的布局。要注意的一点是自定义组件的标签我们要使用包的全面才可以,其他的和Android的系统组件使用方法类似,具体代码如下:

1         <com.example.lizelu.customnavigationbar.CustomNavigationBar
2             android:id="@+id/custom_navigation_bar"
3             android:layout_width="match_parent"
4             android:layout_height="wrap_content"/>

2.在Activity的Java类中,通过id获取我们自定义组件的对象,并实现其相应的回调即可。具体代码如下:

 1     private void setNavigationTitle(String title) {
 2         CustomNavigationBar navigationBar = (CustomNavigationBar) findViewById(R.id.custom_navigation_bar);
 3         navigationBar.setNavigationTitle(title);
 4 
 5         //实现组件上的按钮的接口回调
 6         navigationBar.setCallBackListener(new CustomNavigationBar.OnClickCallBackListener() {
 7             @Override
 8             public void OnClickButton(View v) {
 9                 Toast.makeText(MainActivity.this, "回调执行的方法", Toast.LENGTH_SHORT).show();
10             }
11         });
12     }

到此,自定义组件的实现和调用实现完毕。虽然上述自定义控件虽然比较简单,但是麻雀虽小,五脏俱全。再复杂的自定义控件也是有简单的东西慢慢的拼装而成。所以理解自定义控件的实现原理还是比较重要的。今天的博客就先到这儿,下方是上述Demo在GitHub上的分享地址,需要的小伙伴请自行Clone。

github分享地址:https://github.com/lizelu/AndroidCustomNavigationBar

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏KK的小酒馆

初识自定义控件Android应用界面开发

自定义控件是个大坑,并不能在此以偏概全阐述出它的精髓,笔记仅作为一方面的了解作用。

861
来自专栏潇涧技术专栏

Art of Android Development Reading Notes 3

本节和《Android群英传》中的第五章Scroll分析有关系,建议先阅读该章的总结

771
来自专栏青蛙要fly的专栏

项目需求讨论 — 用Transition做一个漂亮的登录界面

一次在逛Github的时候,看到一个漂亮的登录界面,用的是Transition做的。我就直接贴上地址:

1192
来自专栏上善若水

035android初级篇之[转]android的ViewGroup与View

Android中的View包含了用户交互和显示,类似于Windows操作系统中的window。

993
来自专栏Android Note

Android上的自定义字体 - 扩展TextView

1433
来自专栏何俊林

Android View框架总结(二)View焦点

前言:今天七夕节,笔者先祝大家七夕快乐,无论是否有女朋友,去吃一吨好吃的,年轻多努力才是王道。如果觉得笔者一直以来写的文章,有让你收获那么一点点。可以推荐此公众...

2288
来自专栏AndroidTv

View 动画 Animation 运行原理解析

这次想来梳理一下 View 动画也就是补间动画(ScaleAnimation, AlphaAnimation, TranslationAnimation...)...

3895
来自专栏Android Note

Android上的自定义字体 - 通过XML进行动态字体选择

1296
来自专栏编程思想之路

Android6.0源码分析之View(一)

目前对于view还处于学习阶段,本来打算学习结束之后再写一篇进行总结,但是发现自己自制力太差,学习效率太低,所以在此,边学边写博客,不仅督促自己完成对view的...

1948
来自专栏郭霖

Android ActionBar完全解析,使用官方推荐的最佳导航栏(下)

本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文。 限于篇幅的原因,在上篇文章中我们只学习了ActionBa...

2628

扫码关注云+社区

领取腾讯云代金券