Android Studio自定义组合控件
在Android的开发中,为了能够服用代码,会把有一定共有特点的控件组合在一起定义成一个自定义组合控件。
本文就详细讲述这一过程。虽然这样的View的组合有一个粒度的问题。粒度太大了无法复用,粒度太小了又
达不到很好的复用的效果。不过,这些不在本文的讨论范围,需要读者自己去开发的实践中体会。
实例项目就选择一个登录注册的组件,这组件包括用户名、密码的文本输入框,还有登录和注册的按钮。这里
主要是为了讲解的需要,在选择服用代码的力度上可以不用参考。
默认的当一个新的项目创建以后就会生成一个Activity和与之相应的一个布局文件。这些已经足够使用。
这里假设你默认生成的Activity名称为MainActivity,布局文件为activity_main.xml。
首先,创建一个以LinearLayout为基类的View。这个View的名字就叫做LoginView。
/**
* Created by Bruce on 31/10/15.
*/
public class LoginView extends LinearLayout {
private Context _context;
public LoginView(Context context) {
this(context, null);
}
public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
_context = context;
//...
}
}
代码中包含了一个Context的成员,因为我们在后面需要用到。
之后,创建这个View需要的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<EditText android:id="@+id/userName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="User name" />
<EditText android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/loginButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Login" />
<Button android:id="@+id/signupButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Sign Up" />
</LinearLayout>
</LinearLayout>
按照前文所述,我们要做的是一个登录的界面包含用户名、密码和登录、注册按钮,一共四个子组件。在布局登录、
注册按钮的是时候,需要在横向布局。所以,单独使用了一个新的LinearLayout,设定这个layout的方向(orientation)
为横向(horizental)。两个按钮的宽度都设定为0dp,因为有layout_weight。给layout_weight分别设定了1
之后,这两个按钮将平分他们所在的Linearlayout的宽度。
把这个控件使用在MainActivity中。按照惯例在activity_main中添加控件。只不过这次需要使用的全名称
的限定。就是需要把这个View的完整包路径全部写出来:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<com.example.home.draganddraw.LoginView
android:id="@+id/loginView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.home.draganddraw.LoginView>
<!-- 其他省略 -->
</LinearLayout>
com.example.home.draganddraw.LoginView就是这个View的全名称。同时我们给这个LoginView指定
了id为loginView。在MainActivity的java文件中可以取到这个View:
LoginView loginView = (LoginView)findViewById(R.id.loginView);
这个时候可以run起来这个项目。but,这样又有什么卵用呢?点个按钮也没什么反应。是的,我们需要给这个组合控件
添加代码。我们需要从布局文件中解析出这些单独的控件,EditText和Button。就像是在Activity中经常做的
那样:
View view = LayoutInflater.from(context).inflate(R.layout.view_login, this, true);
EditText userName = (EditText) view.findViewById(R.id.userName);
EditText password = (EditText) view.findViewById(R.id.password);
Button loginButton = (Button) view.findViewById(R.id.loginButton);
Button signupButton = (Button) view.findViewById(R.id.signupButton);
给按钮设置Click Listener。首先让按钮能有反应。那么需要一个OnClickListener。我们这里只有
两个按钮,所以只要在类的级别设定出监听器就可以:
/**
* Created by Bruce on 31/10/15.
*/
public class LoginView extends LinearLayout implements View.OnClickListener {
//...
public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
_context = context;
View view = LayoutInflater.from(context).inflate(R.layout.view_login, this, true);
EditText userName = (EditText) view.findViewById(R.id.userName);
EditText password = (EditText) view.findViewById(R.id.password);
Button loginButton = (Button) view.findViewById(R.id.loginButton);
Button signupButton = (Button) view.findViewById(R.id.signupButton);
loginButton.setOnClickListener(this);
signupButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.loginButton) {
Toast.makeText(MainActivity.this, "Login", Toast.LENGTH_LONG).show();
} else if (v.getId() == R.id.signupButton) {
Toast.makeText(MainActivity.this, "Register", Toast.LENGTH_LONG).show();
}
}
//...
}
用户在点击了按钮之后就会弹出一个Toast来显示你点击的是哪个按钮,“Login”和“Register”。好了,终于有
反映了,但是还是不够的。用户对这个View的操作需要交给Activity做特定的处理,而不是我们直接就把这些
功能在View里全部处理。这样,怎么能打到复用代码的目的呢?所以,我们需要把按钮的点击事件交给MainActivity
来处理。
在iOS里,就是在控件中定义一个Delegate(java的interface),然后在Controller(Activity)中实现
并在组合控件中调用这个实现。一般来说,上面代码中public class LoginView extends LinearLayout implements View.OnClickListener
和方法public void onClick(View v)然后signupButton.setOnClickListener(this);就是这么一个意思。
只不过我们只能看到是怎么用的,但是也可以猜到是怎么定义这个interface的。以上可以总结为:
1. 控件中定义接口。
2. 在Activity的实现。
3. 在控件中使用activity的实现。
定义接口:
/**
* Created by Bruce on 31/10/15.
*/
public class LoginView extends LinearLayout implements View.OnClickListener {
private Context _context;
//...
@Override
public void onClick(View v) {
//...
}
public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) {
//...
}
public interface OnLoginViewClickListener {
void loginViewButtonClicked(View v);
}
}
这里我们定义了接口public interface OnLoginViewClickListener
还有这么一个方法void loginViewButtonClicked(View v);
。
然后定义了这个接口的setter,是啊,activity的实现我们要怎么使用呢?就是通过这个setter给组合控件赋值
过来,然后使用的嘛。
下面在activity中实现这个接口(这个在java里比在ObjC里简单多了好吗):
public class MainActivity extends AppCompatActivity {
private LoginView _loginView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
_loginView = (LoginView)findViewById(R.id.loginView);
_loginView.setOnLoginViewClickListener(new LoginView.OnLoginViewClickListener() {
@Override
public void loginViewButtonClicked(View v) {
if (v.getId() == R.id.loginButton) {
Toast.makeText(MainActivity.this, "Login", Toast.LENGTH_LONG).show();
} else if (v.getId() == R.id.signupButton) {
Toast.makeText(MainActivity.this, "Register", Toast.LENGTH_LONG).show();
}
}
});
}
}
实现这个接口的时候,直接在LoginView
的实现上把接口new出来一个实例就可以。这个东西在ObjC里墨迹的半死。
现在还有人说java实现个回调太麻烦,这个有OC复杂吗?
要在LoginView中使用这个接口的实现就更加简单了。直接上代码:
/**
* Created by Bruce on 31/10/15.
*/
public class LoginView extends LinearLayout implements View.OnClickListener {
private Context _context;
private OnLoginViewClickListener _onLoginViewClickListener;
//...
@Override
public void onClick(View v) {
if (_onLoginViewClickListener != null) {
_onLoginViewClickListener.loginViewButtonClicked(v);
}
}
public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) {
_onLoginViewClickListener = loginViewClickListener;
}
public interface OnLoginViewClickListener {
void loginViewButtonClicked(View v);
}
}
在LoginView中定义接口的成员private OnLoginViewClickListener _onLoginViewClickListener;
。
在setter中把这个接口的实现赋值给这个LoginView的成员变量,完事儿:
public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) {
_onLoginViewClickListener = loginViewClickListener;
}
这个时候再次运行项目,点击按钮之后出现的Toast就是我们在activity里的实现了。
到这里,这个组合控件就已经有一定的使用价值了。定义了一个接口,这个接口的实现也在activity里定义了出来。
把这个控件放在任何一个需要登录的actvity里都可以把用户点击按钮之后的操作给activity实现,想怎么实现
都可以。
但是,我们现在需要把这个控件的用户名和密码的输入框的hint
也放在外面去实现。这个需求并不复杂。既然接口的
实现可以在setter里实现,那么hint当然也是可以的。没错,但是这并不符合android的实现要求–什么都在
xml文件中定义。这样也更加便于管理。同事也方便添加,直接在LoginView的xml引用中添加你定义的属性
就完成了。如:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<!--<include layout="@layout/content_main" />-->
<com.example.home.draganddraw.LoginView
android:id="@+id/loginView"
app:UserNameHint="yo bro"
app:PasswordHint="hey wsp"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.home.draganddraw.LoginView>
</LinearLayout>
属性app:UserNameHint="yo bro"
和app:PasswordHint="hey wsp"
就是我们自定义的属性。
直接像系统内置的属性使用一样就可以。这样比隐藏在代码中的setter方便了很多。
添加,在values文件夹下添加attrs.xml文件。然后在文件中添加:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LoginView">
<attr name="UserNameHint" format="string"/>
<attr name="PasswordHint" format="string"/>
</declare-styleable>
</resources>
我们要给LoginView两个属性,一个是UserNameHint
一个是PasswordHint
。后面的format是这个属性
的格式,这里都是string。
定义了属性,写在xml文件里还是不管用的。需要我们在自定义的view里添加代码才行。代码:
public LoginView(Context context, AttributeSet attrs) {
super(context, attrs);
_context = context;
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.LoginView, defStyle, 0);
CharSequence userNameHint = typedArray.getText(R.styleable.LoginView_UserNameHint);
CharSequence passwordHint = typedArray.getText(R.styleable.LoginView_PasswordHint);
View view = LayoutInflater.from(context).inflate(R.layout.view_login, this, true);
EditText userName = (EditText) view.findViewById(R.id.userName);
EditText password = (EditText) view.findViewById(R.id.password);
Button loginButton = (Button) view.findViewById(R.id.loginButton);
Button signupButton = (Button) view.findViewById(R.id.signupButton);
userName.setHint(userNameHint);
password.setHint(passwordHint);
loginButton.setOnClickListener(this);
signupButton.setOnClickListener(this);
}
用TypedArray
来完成解析和取值的工作。之后给EditText分别设定hint。
再次运行项目就可以看到你设定的hint出现了。但是,有一个错误。是的有错误。TypedArray
需要回收
所以取到所需要的值以后,typedArray.recycle();
回收TypedArray实例。
Android Studio自定义组合控件的更多相关文章
- Android中自定义组合控件
Android中自定义控件的情况非常多,一般自定义控件可以分为两种:继承控件及组合控件.前者是通过继承View或其子类,重写方法实现自定义的显示及事件处理方式:后者是通过组合已有的控件,来实现结构的简 ...
- Android自定义组合控件详细示例 (附完整源码)
在我们平时的Android开发中,有时候原生的控件无法满足我们的需求,或者经常用到几个控件组合在一起来使用.这个时候,我们就可以根据自己的需求创建自定义的控件了,一般通过继承View或其子类来实现. ...
- Android自定义控件之自定义组合控件
前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...
- Android 手机卫士--自定义组合控件构件布局结构
由于设置中心条目中的布局都很类似,所以可以考虑使用自定义组合控件来简化实现 本文地址:http://www.cnblogs.com/wuyudong/p/5909043.html,转载请注明源地址. ...
- Android开发之自定义组合控件
自定义组合控件的步骤1.自定义一个View,继承ViewGroup,比如RelativeLayout2.编写组合控件的布局文件,在自定义的view中加载(使用View.inflate())3.自定义属 ...
- Android开发学习笔记-自定义组合控件的过程
自定义组合控件的过程 1.自定义一个View 一般来说,继承相对布局,或者线性布局 ViewGroup:2.实现父类的构造方法.一般来说,需要在构造方法里初始化自定义的布局文件:3.根据一些需要或者需 ...
- Android自定义控件之自定义组合控件(三)
前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...
- Android View体系(十)自定义组合控件
相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...
- 014 Android 自定义组合控件
1.需求介绍 将已经编写好的布局文件,抽取到一个类中去做管理,下次还需要使用类似布局时,直接使用该组合控件的对象. 优点:可复用. 例如要重复利用以下布局: <RelativeLayout an ...
随机推荐
- 前端基础之BOM和DOM day52
前端基础之BOM和DOM 前戏 到目前为止,我们已经学过了JavaScript的一些简单的语法.但是这些简单的语法,并没有和浏览器有任何交互. 也就是我们还不能制作一些我们经常看到的网页的一些交互 ...
- u-boot之怎么实现分区
启动参数bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0中kernel在哪定义,为什么可以直接引用?针对这个问题展开思考最终定位到 ...
- 对于读txt文件一点总结
txt 内容 中间有比如如空格,制表符(tab)在txt为空格符(Spaces).回车符.换行符,有空字符串等情况,在读取过滤中要充分考虑到 1:打开文件 var sr=new StreamReade ...
- C# 使用printDocument1.Print打印时不显示 正在打印对话框
C#使用printDocument1.Print打印时不显示正在打印对话框有两种方法 第一种,使用PrintController PrintController printControll ...
- andorid 多线程handler用法
.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...
- 初学者的分布式Python爬虫教程
下面是一个超级计算机的排行榜,如果我们能拥有其中任意一个,那么我们就不需要搞什么分布式系统.可是我们买不起,即使买得起,也交不起电费,所以我们只好费脑子搞分布式. 分布式的本质就如上期提到的一个概念: ...
- AppStore企业账号打包发布APP流程详解
一.通过企业账号申请证书 1 Certificate Signing Request (CSR)文件 在Mac系统中进入“钥匙串访问”,选择“钥匙串访问”-“证书助理”-“从证书颁发机构请求证书…”, ...
- Tinyos学习笔记(一)
简述:发送和接受数据的程序分别烧录到两个节点上,发送方发送流水灯数据,接受方接受数据并实现流水灯 1.发送和接受程序用到的组件及其接口如图(通过make telosb docs获得)所示: 2.发 ...
- sqli盲注自用脚本
盲注脚本 # -*- coding:utf-8 -*- import requests import re url = "http://123.206.87.240:8002/chengji ...
- swift 官方获取JSON 数据方法
var url = NSURL(string: "http://www.weather.com.cn/data/sk/101120501.html") var data = NSD ...