當需要用戶填充一個較長的表單時,開發者或許會找不到頭緒。在接下來的這個例子中,我們會使用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。