当需要用户填充一个较长的表单时,开发者或许会找不到头绪。在接下来的这个例子中,我们会使用Gallery控件创建一个具有多个表单项的用户注册表单。最终效果如下图所示。
要实现上述的向导表单,需要创建一个命名为CreateAccountActivity的Activity。我们为上述Activity使用Theme.Dialog样式。在该Activity中,我们会创建一个Gallery对象,并且用一个Adapter填充这个Gallery。因为该Adapter需要与Activity交互,因此我们使用Delegate委托接口。
先创建每个页面的公用视图,XML文件内容如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="250dp" android:layout_height="220dp" android:background="#AAAAAA">
<!--在该LinearLayout中放置要显示的表单项-->
<LinearLayout android:id="@+id/create_account_form" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:orientation="vertical" android:paddingLeft="10dp" android:paddingRight="10dp" android:paddingTop="10dp">
<!--在LinearLayout第一个子视图中显示表单标题-->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Account creation" android:textColor="#000000" android:textSize="20sp" android:textStyle="bold"/>
</LinearLayout>
<!--Next按钮用于切换到下一个页面-->
<Button android:id="@+id/create_account_next" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:gravity="center" android:text="Next" android:textSize="12sp"/>
<!--这个按钮只在最后一个页面显示,用于提交表单-->
<Button android:id="@+id/create_account_create" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/create_account_form" android:gravity="center" android:paddingRight="45dp" android:text="Create Account" android:textSize="12sp"/>
</RelativeLayout>
既然有了公用视图的布局文件,我们就可以创建Adapter的代码。我们将该Adapter命名为CreateAccountAdapter,继承自BaseAdapter。代码如下:
package com.example.huangfei.hack2;
import android.content.Context;
import android.graphics.Color;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.example.huangfei.hack2.model.Account;
import com.example.huangfei.hack2.model.Countries;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/** * Created by huangfeihong on 2015/11/19. */
public class CreateAccountAdapter extends BaseAdapter {
/** * 该接口用来与CreateAccountActivity进行交互 */
public static interface CreateAccountDelegate {
int FORWARD = 1;
int BACKWARD = -1;
void scroll(int type);//该方法当点击”Next“按钮时执行
void processForm(Account account);//当用户提交表单时,执行该方法
}
public static final String FULL_NAME_KEY = "fullname";
public static final String EMAIL_KEY = "email";
public static final String PASSWORD_KEY = "password";
public static final String GENDER_KEY = "gender";
public static final String CITY_KEY = "city";
public static final String COUNTRY_KEY = "country";
public static final String ZIP_KEY = "zipcode";
private static final int FORMS_QTY = 4;
private Context mContext;
private LayoutInflater mInflator;
private CreateAccountDelegate mDelegate;
private HashMap<String, String> mFormData;
private Account mAccount;
public CreateAccountAdapter(Context ctx) {
mContext = ctx;
mInflator = LayoutInflater.from(ctx);
mFormData = new HashMap<String, String>();
mAccount = new Account();
}
@Override
public int getCount() {
return FORMS_QTY;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
public void setDelegate(CreateAccountDelegate delegate) {
mDelegate = delegate;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
//填充自定义View
convertView = mInflator.inflate(
R.layout.create_account_generic_row, parent, false);
}
//获取放置表单项的LinearLayout
LinearLayout formLayout = (LinearLayout) convertView
.findViewById(R.id.create_account_form);
//最后一页不显示”Next“按钮
View nextButton = convertView
.findViewById(R.id.create_account_next);
if (position == FORMS_QTY - 1) {
nextButton.setVisibility(View.GONE);
} else {
nextButton.setVisibility(View.VISIBLE);
}
if (mDelegate != null) {
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDelegate.scroll(CreateAccountDelegate.FORWARD);
}
});
}
//仅在最后一页显示提交表单按钮
Button createButton = (Button) convertView
.findViewById(R.id.create_account_create);
if (position == FORMS_QTY - 1) {
createButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
processForm();
}
});
createButton.setVisibility(View.VISIBLE);
} else {
createButton.setVisibility(View.GONE);
}
//根据页面位置填充LinearLayout
switch (position) {
case 0:
populateFirstForm(formLayout);
break;
case 1:
populateSecondForm(formLayout);
break;
case 2:
populateThirdForm(formLayout);
break;
case 3:
populateFourthForm(formLayout);
break;
}
return convertView;
}
/** * 创建第一个表单 */
private void populateFirstForm(LinearLayout formLayout) {
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_full_name_title)));
EditText nameEditText = createEditText(
mContext.getString(R.string.account_create_full_name_hint),
InputType.TYPE_CLASS_TEXT, EditorInfo.IME_ACTION_NEXT, false,
FULL_NAME_KEY);
if (mFormData.get(FULL_NAME_KEY) != null) {
nameEditText.setText(mFormData.get(FULL_NAME_KEY));
}
formLayout.addView(nameEditText);
formLayout.addView(createErrorView(FULL_NAME_KEY));
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_email_title)));
EditText emailEditText = createEditText(
mContext.getString(R.string.account_create_email_hint),
InputType.TYPE_CLASS_TEXT, EditorInfo.IME_ACTION_NEXT, true,
EMAIL_KEY);
if (mFormData.get(EMAIL_KEY) != null) {
emailEditText.setText(mFormData.get(EMAIL_KEY));
}
formLayout.addView(emailEditText);
formLayout.addView(createErrorView(EMAIL_KEY));
}
/** * 创建第二个表单 */
private void populateSecondForm(LinearLayout formLayout) {
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_password_title)));
EditText passwordEditText = createEditText(
mContext.getString(R.string.account_create_password_hint),
InputType.TYPE_CLASS_TEXT, EditorInfo.IME_ACTION_DONE, false,
PASSWORD_KEY);
//设置输入框文字展示形式
passwordEditText.setTransformationMethod(new PasswordTransformationMethod());
formLayout.addView(passwordEditText);
formLayout.addView(createErrorView(PASSWORD_KEY));
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_gender_title)));
Spinner spinner = new Spinner(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.bottomMargin = 17;
spinner.setLayoutParams(params);
final ArrayAdapter<CharSequence> adapter = ArrayAdapter
.createFromResource(mContext, R.array.sexes_array,
android.R.layout.simple_spinner_item);
spinner.setAdapter(adapter);
spinner.setPrompt(mContext
.getString(R.string.account_create_sex_spinner_prompt));
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int pos, long id) {
mFormData.put(GENDER_KEY, adapter.getItem(pos).toString());
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
if (mFormData.get(GENDER_KEY) != null) {
spinner.setSelection(adapter.getPosition(mFormData
.get(GENDER_KEY)));
}
formLayout.addView(spinner);
}
/** * 创建第三个表单 */
private void populateThirdForm(LinearLayout formLayout) {
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_city_title)));
EditText cityEditText = createEditText(
mContext.getString(R.string.account_create_city_hint),
InputType.TYPE_CLASS_TEXT, EditorInfo.IME_ACTION_DONE, false,
CITY_KEY);
if (mFormData.get(CITY_KEY) != null) {
cityEditText.setText(mFormData.get(CITY_KEY));
}
formLayout.addView(cityEditText);
formLayout.addView(createErrorView(CITY_KEY));
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_country_title)));
Spinner spinner = new Spinner(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.bottomMargin = 17;
spinner.setLayoutParams(params);
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
mContext, android.R.layout.simple_spinner_item,
Countries.COUNTRIES);
spinner.setAdapter(adapter);
spinner.setPrompt(mContext
.getString(R.string.account_create_country_spinner_prompt));
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view,
int pos, long id) {
mFormData.put(COUNTRY_KEY, Countries.COUNTRY_CODES[pos]);
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
});
if (mFormData.get(COUNTRY_KEY) != null) {
List<String> array = Arrays.asList(Countries.COUNTRY_CODES);
spinner.setSelection(array.indexOf(mFormData.get(COUNTRY_KEY)));
}
formLayout.addView(spinner);
}
/** * 创建第四个表单 */
private void populateFourthForm(LinearLayout formLayout) {
formLayout.addView(createTitle(mContext
.getString(R.string.account_create_zip_title)));
EditText zipEditText = createEditText(
mContext.getString(R.string.account_create_zip_hint),
InputType.TYPE_CLASS_TEXT, EditorInfo.IME_ACTION_GO, true,
ZIP_KEY);
if (mFormData.get(ZIP_KEY) != null) {
zipEditText.setText(mFormData.get(ZIP_KEY));
}
formLayout.addView(zipEditText);
formLayout.addView(createErrorView(ZIP_KEY));
}
/** * 创建标题 */
private TextView createTitle(String text) {
TextView ret = new TextView(mContext);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
ret.setLayoutParams(params);
ret.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);
ret.setTextColor(Color.BLACK);
ret.setText(text);
return ret;
}
/** * 创建输入框 */
private EditText createEditText(String hint, int inputType,
int imeOption, boolean shouldMoveToNext, final String key) {
EditText ret = new EditText(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ret.setLayoutParams(params);
ret.setHint(hint);
ret.setInputType(inputType);//设置输入框输入模式
ret.setImeOptions(imeOption);//设置软键盘模式
if (shouldMoveToNext) {
ret.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
if (mDelegate != null) {
//软键盘中的Next按钮
if (EditorInfo.IME_ACTION_NEXT == actionId) {
mDelegate.scroll(CreateAccountDelegate.FORWARD);
} else {
processForm();
}
return true;
} else {
return false;
}
}
});
}
ret.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
mFormData.put(key, s.toString());
}
});
return ret;
}
/** * 提交表单 */
private void processForm() {
if (mDelegate != null) {
Account account = new Account();
account.setCity(mFormData.get(CITY_KEY));
account.setCountry(mFormData.get(COUNTRY_KEY));
account.setEmail(mFormData.get(EMAIL_KEY));
account.setGender(mFormData.get(GENDER_KEY));
account.setName(mFormData.get(FULL_NAME_KEY));
account.setPassword(mFormData.get(PASSWORD_KEY));
account.setPostalCode(mFormData.get(ZIP_KEY));
mDelegate.processForm(account);
}
}
/** * 创建错误提醒文字 */
private TextView createErrorView(String key) {
TextView ret = new TextView(mContext);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.topMargin = 10;
params.bottomMargin = 10;
ret.setLayoutParams(params);
ret.setTextColor(Color.RED);
ret.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
HashMap<String, String> errors = mAccount.getErrors();
if (errors != null && errors.containsKey(key)) {
ret.setText(errors.get(key));
ret.setVisibility(View.VISIBLE);
} else {
ret.setVisibility(View.INVISIBLE);
}
return ret;
}
/** * 展示错误信息 */
public void showErrors(Account account) {
mAccount = account;
mFormData.clear();
mFormData.put(FULL_NAME_KEY, mAccount.getName());
mFormData.put(EMAIL_KEY, mAccount.getEmail());
mFormData.put(GENDER_KEY, mAccount.getGender());
mFormData.put(CITY_KEY, mAccount.getCity());
mFormData.put(COUNTRY_KEY, mAccount.getCountry());
mFormData.put(ZIP_KEY, mAccount.getPostalCode());
notifyDataSetChanged();
}
}
我们还有一个模块没有讲到,那就是用来实现CreateAccountDelegate接口的CreateAccountActivity类,代码如下:
package com.example.huangfei.hack2;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.widget.Gallery;
import android.widget.Toast;
import com.example.huangfei.hack2.model.Account;
import java.util.HashMap;
/** * Created by huangfeihong on 2015/11/19. * 用于跟踪用户当前所在的页面,并处理页面跳转的逻辑 */
public class CreateAccountActivity extends Activity implements CreateAccountAdapter.CreateAccountDelegate {
private Gallery mGallery;
private CreateAccountAdapter mAdapter;
private int mGalleryPosition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.create_account);
mGallery = (Gallery) findViewById(R.id.create_account_gallery);
mAdapter = new CreateAccountAdapter(this);
mGallery.setAdapter(mAdapter);
mGalleryPosition = 0;
}
/** * 在onResume()方法中将当前Activity设置为Adapter的委托, * 并且在onPause()方法中将委托置空 */
@Override
protected void onResume() {
super.onResume();
mAdapter.setDelegate(this);
}
@Override
protected void onPause() {
super.onPause();
mAdapter.setDelegate(null);
}
/** * 重写onBackPressed()方法,用于返回上一个页面 */
@Override
public void onBackPressed() {
if (mGalleryPosition > 0) {
scroll(BACKWARD);
} else {
super.onBackPressed();
}
}
/** * 在该方法中,Activity可以根据参数将Gallery移动到下一个页面或者上一个页面中 * 遗憾的是,我们无法为Gallery控件的页面切换添加动画效果。我唯一想到方法是发送 * KeyEvent.KEYCODE_DPAD_RIGHT事件,虽然这是投机取巧,却也管用。 */
@Override
public void scroll(int type) {
switch (type) {
case FORWARD:
default:
if (mGalleryPosition < mGallery.getCount() - 1) {
mGallery.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(0,
0));
mGalleryPosition++;
}
break;
case BACKWARD:
if (mGalleryPosition > 0) {
mGallery.onKeyDown(KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(0,
0));
mGalleryPosition--;
}
}
}
@Override
public void processForm(Account account) {
HashMap<String, String> errors = account.getErrors();
String email = account.getEmail();
if (TextUtils.isEmpty(email)) {
errors.put(CreateAccountAdapter.EMAIL_KEY, "E-mail is empty");
} else if (email.toLowerCase().equals("[email protected]")) {
errors.put(CreateAccountAdapter.EMAIL_KEY,
"E-mail is already taken");
}
if (errors.isEmpty()) {
Toast.makeText(this, "Form ok!", Toast.LENGTH_SHORT).show();
finish();
} else {
mAdapter.showErrors(account);
mGallery.setSelection(0);
mGalleryPosition = 0;
}
}
}
使用Gallery控件创建向导表单可以简化用户填写较长表单的流程。将表单放在不同页面中,并且利用Gallery控件的默认动画添加悦目的效果,可以使用户在填写表单的过程中更愉悦些。
根据需要,我们其实也可以使用ViewPager 开发相同的功能,只是其Adapter返回的不是View而是Fragment。
代码地址