本文共 7547 字,大约阅读时间需要 25 分钟。
在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需要的布局文件:按照前文所述,我们要做的是一个登录的界面包含用户名、密码和登录、注册按钮,一共四个子组件。在布局登录、
注册按钮的是时候,需要在横向布局。所以,单独使用了一个新的LinearLayout,设定这个layout的方向(orientation) 为横向(horizental)。两个按钮的宽度都设定为0dp,因为有layout_weight。给layout_weight分别设定了1 之后,这两个按钮将平分他们所在的Linearlayout的宽度。把这个控件使用在MainActivity中。按照惯例在activity_main中添加控件。只不过这次需要使用的全名称
的限定。就是需要把这个View的完整包路径全部写出来: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);
。
下面在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里墨迹的半死。
要在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;
。
public void setOnLoginViewClickListener(OnLoginViewClickListener loginViewClickListener) { _onLoginViewClickListener = loginViewClickListener;}
这个时候再次运行项目,点击按钮之后出现的Toast就是我们在activity里的实现了。
到这里,这个组合控件就已经有一定的使用价值了。定义了一个接口,这个接口的实现也在activity里定义了出来。
把这个控件放在任何一个需要登录的actvity里都可以把用户点击按钮之后的操作给activity实现,想怎么实现 都可以。但是,我们现在需要把这个控件的用户名和密码的输入框的hint
也放在外面去实现。这个需求并不复杂。既然接口的
属性app:UserNameHint="yo bro"
和app:PasswordHint="hey wsp"
就是我们自定义的属性。
添加,在values文件夹下添加attrs.xml文件。然后在文件中添加:
我们要给LoginView两个属性,一个是UserNameHint
一个是PasswordHint
。后面的format是这个属性
定义了属性,写在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实例。