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实例。

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处!
















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/4929541.html ,如需转载请自行联系原作者
相关文章
|
3月前
|
SQL 人工智能 Dart
Android Studio的插件生态非常丰富
Android Studio的插件生态非常丰富
183 1
|
3月前
|
Ubuntu Linux Android开发
Android Studio支持多种操作系统
Android Studio支持多种操作系统
167 1
|
13天前
|
前端开发 Java 编译器
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
71 36
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
|
3月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
56 1
|
13天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
37 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
3月前
|
前端开发 数据处理 Android开发
Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍
本文深入探讨了Flutter前端开发中的调试技巧与工具使用方法,涵盖调试的重要性、基本技巧如打印日志与断点调试、常用调试工具如Android Studio/VS Code调试器和Flutter Inspector的介绍,以及具体操作步骤、常见问题解决、高级调试技巧、团队协作中的调试应用和未来发展趋势,旨在帮助开发者提高调试效率,提升应用质量。
89 8
|
3月前
|
数据可视化 开发工具 Android开发
Android Studio
Android Studio
248 1
|
3月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
6月前
|
Java Android开发 芯片
使用Android Studio导入Android源码:基于全志H713 AOSP,方便解决编译、编码问题
本文介绍了如何将基于全志H713芯片的AOSP Android源码导入Android Studio以解决编译和编码问题,通过操作步骤的详细说明,展示了在Android Studio中利用代码提示和补全功能快速定位并修复编译错误的方法。
351 0
使用Android Studio导入Android源码:基于全志H713 AOSP,方便解决编译、编码问题
|
6月前
|
Dart 开发工具 Android开发
Android Studio导入Flutter项目提示Dart SDK is not configured
Android Studio导入Flutter项目提示Dart SDK is not configured
595 4

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    Android历史版本与APK文件结构
  • 3
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 6
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 8
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 9
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 1
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    24
  • 2
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    32
  • 3
    Android历史版本与APK文件结构
    119
  • 4
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    27
  • 5
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    23
  • 6
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    55
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    37
  • 8
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    71
  • 9
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    117
  • 10
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
    29