自定义控件的目的有很多,比如系统控件满足不了需求时,我们会想到通过自定义控件来满足需求。其实有的时候为了功能的复用我们也会去自定义控件,把经常要用的或以后要用的与UI相关的功能封装到自定义控件中,让它成为独立的功能,当然为了灵活的控制其中的可变部分,自定义的控件应该预留接口(这里说的接口不是Java中的Interface,是控制可变部分的方式,比如方法之类的)。
接下来的案例用普通的GridView+Adapter也可以实现效果图中的功能,但为了将来复用,我们可以把效果图中的键盘逻辑封装到自定义控件中。
(1)主界面布局 activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <GridView android:id="@+id/gv_keys" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:numColumns="8" /> <SeekBar android:id="@+id/sb_level" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:max="9" /> </RelativeLayout>(2)按健布局 key_layout.xml
<?xml version="1.0" encoding="utf-8"?> <Button xmlns:android="http://schemas.android.com/apk/res/android" android:id = "@+id/btn_key" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#1773CE" android:textSize = "22sp" android:textStyle="bold" android:background="@drawable/key_bg"> </Button>其中使用了一个带选择器的背景,drawable/key_bg.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/word_pressed" android:state_pressed="true"/> <item android:drawable="@drawable/word"/> </selector>背景选择器中使用了两张图片drawable-hdpi/word.png,drawable-hdpi/word_pressed.png,分别会在普通状态与按下状态显示。
(1)XML中配置数据
<resources> <string name="app_name">InputKeyBoard</string> <string name="hello_world">Hello world!</string> <string name="action_settings">Settings</string> <string name = "all_keys">"应有尽有自吹自擂自说自话百发百中百依百顺百战百胜半信半疑不屈不挠不知不觉不伦不类不折不扣大吹大擂能屈能伸蹑手蹑脚善始善终十全十美惟妙惟肖畏首畏尾无缘无故无影无踪载歌载舞兴高采烈怒气冲冲聚精会神自言自语千钧一发雨后春笋琳琅满目顶天立地千方百计小心翼翼焕然一新胸有成竹草木皆兵赤膊上阵用兵如神目不转睛鸿鹄之志一日十行问心无愧"</string> </resources>values/arrays.xml,其中一共有10条数据,代表有10关,而每条数据代表每一关的正确答案,将来要保证第一关的键盘上一定有正确答案在,只不过正确答案中的每个字是顺序打乱的。
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name = "correntKeys"> <item>"兴高采烈"</item> <item>"怒气冲冲"</item> <item>"聚精会神"</item> <item>"自言自语"</item> <item>"千钧一发"</item> <item>"雨后春笋"</item> <item>"琳琅满目"</item> <item>"顶天立地"</item> <item>"千方百计"</item> <item>"小心翼翼"</item> </string-array> </resources>
(2)按键数据实体类设计
package com.kedi.inputkeyboard; import android.widget.Button; /** * 按键实体类 * * @author 张科勇 * */ public class Key { //按健在键盘中的位置 private int keyPosition; //按键上的文字 private String keyword; //显示按键文字的Button控件 private Button keyView; public Key(){} public Key(int keyPosition, String keyword, Button keyView) { this.keyPosition = keyPosition; this.keyword = keyword; this.keyView = keyView; } public int getKeyPosition() { return keyPosition; } public void setKeyPosition(int keyPosition) { this.keyPosition = keyPosition; } public String getKeyword() { return keyword; } public void setKeyword(String keyword) { this.keyword = keyword; } public Button getKeyView() { return keyView; } public void setKeyView(Button keyView) { this.keyView = keyView; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + keyPosition; result = prime * result + ((keyView == null) ? 0 : keyView.hashCode()); result = prime * result + ((keyword == null) ? 0 : keyword.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Key other = (Key) obj; if (keyPosition != other.keyPosition) return false; if (keyView == null) { if (other.keyView != null) return false; } else if (!keyView.equals(other.keyView)) return false; if (keyword == null) { if (other.keyword != null) return false; } else if (!keyword.equals(other.keyword)) return false; return true; } }
(3)封装与获取数据工具类设计
package com.kedi.inputkeyboard; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; /** * 造数据的工具类 * @author 张科勇 * */ public class DataUtil { //总按键数,一般都是确定的 public static final int TOTAL_KEYS_NUM = 24; public static List<Key> getDatas(String allKeys,String correctKeys){ List<Key> dataList = new ArrayList<Key>(); //1、封装正确答案对应的Key char[] correctKeyChars = correctKeys.toCharArray(); for(int i = 0;i<correctKeyChars.length;i++){ Key correctKey = new Key(); correctKey.setKeyword(correctKeyChars[i]+""); dataList.add(correctKey); } //2、封装干扰答案对应的Key //要生成的干扰答案的个数 char[] allKeyChars = allKeys.toCharArray(); Random random = new Random(); while(true){ int randomInt = random.nextInt(allKeyChars.length); char randomkey = allKeyChars[randomInt]; Key disturbKey = new Key(); disturbKey.setKeyword(randomkey+""); //过滤重复字 if(!dataList.contains(disturbKey)){ dataList.add(disturbKey); } if(dataList.size()==TOTAL_KEYS_NUM){ break; } } //3.乱序集合 Collections.shuffle(dataList); return dataList; } }
package com.kedi.inputkeyboard; import java.util.List; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; /** * 键盘控件适器 * @author 张科勇 * */ public class KeyBoardAdapter extends BaseAdapter { //上下文 private Context mContext; //数据集合 private List<Key> mDatas; public KeyBoardAdapter(Context mContext, List<Key> mDatas) { this.mContext = mContext; this.mDatas = mDatas; } @Override public int getCount() { return mDatas.size(); } @Override public Object getItem(int position) { return mDatas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = View.inflate(mContext, R.layout.key_layout, null); Button keyView = (Button) view.findViewById(R.id.btn_key); Key key = mDatas.get(position); keyView.setText(key.getKeyword()); key.setKeyPosition(position); key.setKeyView(keyView); return view; } }
package com.kedi.inputkeyboard; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.widget.GridView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; /** * 键盘控制类 * @author 张科勇 * */ public class MainActivity extends Activity { //键盘控件 private GridView mKeyBoardGv; //键盘急控件适配器 private KeyBoardAdapter mAdapter; //数据集合 private List<Key> mDatas = new ArrayList<Key>(); //拖动条 private SeekBar mLevelSb; // 当前关数 private int level = 0; //所有关对应的正确答案数组,从XML中获得 private String[] mCorrentKeys; //所有关用到的文字库字符串 private String mAllKeys; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { initDatas(); initViews(); bindDataToView(); handleEvent(); } /** * 初始化数据 */ private void initDatas() { //从string.xml文件中获取文字库字符串 mAllKeys = getResources().getString(R.string.all_keys); //从arrays.xml中获取所有关对应的正确答案数组, mCorrentKeys = getResources().getStringArray(R.array.correntKeys); //调用数据工具类,获取当前关对应的数据集合 mDatas = DataUtil.getDatas(mAllKeys, mCorrentKeys[level]); } /** * 初始化View */ private void initViews() { mKeyBoardGv = (GridView) findViewById(R.id.gv_keys); mLevelSb = (SeekBar) findViewById(R.id.sb_level); } /** * Data与View绑定 */ private void bindDataToView() { mAdapter = new KeyBoardAdapter(this, mDatas); mKeyBoardGv.setAdapter(mAdapter); } /** * 处理交互事件的方法 */ private void handleEvent() { mLevelSb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { level = progress; // 获取每一关的数据 mDatas = DataUtil.getDatas(mAllKeys, mCorrentKeys[level]); // 刷新键盘文字 bindDataToView(); } }); } }上面就是使用Android系统原生控件GridView实现的步骤,接下来使用自定义控件来重构代码。
首先考虑GridView已经能基本满足我们的界面要求,所以自定键盘控件可以基于GridView进行定制。为了保证自定义控件KeyBoardView尽可能的独立,也就是尽可以的少的依赖其它类,所以打算把之前DataUtil中的功能,KeyBoardAdapter的功能都封装到KeyBoardViewView中,并对外提供获取数据和设置数据接口方法,当然如果以后再有扩展需求,也可以把事件之类的进行封装与回调。
修改处:
1、KeyBoardView自定义控件类。
这个控件目前没有扩展其它功能,主要是把之前实现的代码基本都移到了这个控件中。
package com.kedi.inputkeyboard; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.GridView; /** * 自定义键盘控件 * * @author 张科勇 * */ public class KeyBoardView extends GridView { // 所有关对应的正确答案数组,从XML中获得 private String[] mCorrentKeys; // 所有关用到的文字库字符串 private String mAllKeys; // 数据集合 private List<Key> mDatas = new ArrayList<Key>(); // 键盘急控件适配器 private KeyBoardAdapter mAdapter; // 总按键数,一般都是确定的 public static final int TOTAL_KEYS_NUM = 24; // get方法 public String[] getCorrentKeys() { return mCorrentKeys; } // get方法 public String getAllKeys() { return mAllKeys; } public void setDatas(List<Key> mDatas) { this.mDatas = mDatas; if (mAdapter != null) { mAdapter.notifyDataSetChanged(); } } public KeyBoardView(Context context) { this(context, null); } public KeyBoardView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public KeyBoardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化方法 */ private void init() { initDatas(); bindDataToView(); } /** * 初始化数据 */ private void initDatas() { // 从string.xml文件中获取文字库字符串 mAllKeys = getResources().getString(R.string.all_keys); // 从arrays.xml中获取所有关对应的正确答案数组, mCorrentKeys = getResources().getStringArray(R.array.correntKeys); // 调用数据工具类,获取当前关对应的数据集合 mDatas = getDatas(mAllKeys, mCorrentKeys[0]); } /** * Data与View绑定 */ private void bindDataToView() { mAdapter = new KeyBoardAdapter(); setAdapter(mAdapter); } /** * 键盘控件适器 * * @author 张科勇 * */ public class KeyBoardAdapter extends BaseAdapter { @Override public int getCount() { return mDatas.size(); } @Override public Object getItem(int position) { return mDatas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = View.inflate(getContext(), R.layout.key_layout, null); Button keyView = (Button) view.findViewById(R.id.btn_key); Key key = mDatas.get(position); keyView.setText(key.getKeyword()); key.setKeyPosition(position); key.setKeyView(keyView); return view; } } /** * 将字符串数据封装成数据实体集合返回 * * @param allKeys * 文字库字符串 * @param correctKeys * 每一关正确答案字符串 * @return 数据实体集合 */ public List<Key> getDatas(String allKeys, String correctKeys) { List<Key> dataList = new ArrayList<Key>(); // 1、封装正确答案对应的Key char[] correctKeyChars = correctKeys.toCharArray(); for (int i = 0; i < correctKeyChars.length; i++) { Key correctKey = new Key(); correctKey.setKeyword(correctKeyChars[i] + ""); dataList.add(correctKey); } // 2、封装干扰答案对应的Key // 要生成的干扰答案的个数 char[] allKeyChars = allKeys.toCharArray(); Random random = new Random(); while (true) { int randomInt = random.nextInt(allKeyChars.length); char randomkey = allKeyChars[randomInt]; Key disturbKey = new Key(); disturbKey.setKeyword(randomkey + ""); // 过滤重复字 if (!dataList.contains(disturbKey)) { dataList.add(disturbKey); } if (dataList.size() == TOTAL_KEYS_NUM) { break; } } // 3.乱序集合 Collections.shuffle(dataList); return dataList; } }
2、activity_main.xml主布局界面。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.kedi.inputkeyboard.KeyBoardView android:id="@+id/gv_keys" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:numColumns="8" /> <SeekBar android:id="@+id/sb_level" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:max="9" /> </RelativeLayout>3、MainActivity类。
之前的与数据,适配等相关的代码都移到自定义控件中,MainActivity类变的轻松很多,可以回头和之前的MainActivity相比一下。
package com.kedi.inputkeyboard; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; /** * 使用自定义实现键盘类演示Activity * * @author 张科勇 * */ public class MainActivity extends Activity { // 键盘控件 private KeyBoardView mKeyBoardGv; // 拖动条 private SeekBar mLevelSb; // 当前关数 private int level = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mKeyBoardGv = (KeyBoardView) findViewById(R.id.gv_keys); mLevelSb = (SeekBar) findViewById(R.id.sb_level); mLevelSb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { level = progress; //获取对应关的数据 List<Key> datas = mKeyBoardGv.getDatas(mKeyBoardGv.getAllKeys(), mKeyBoardGv.getCorrentKeys()[level]); //适配当前关键盘 mKeyBoardGv.setDatas(datas); } }); } }尤其是在SeekBar控制的时候,需要的数据什么的都可以从自定义控件中调,而自定控件则不依赖它,做到了相对独立,提高了功能块的复用性。上面案例数据是成语,只要我们愿意,换一下XML中的数据,马上另一个项目的键盘就出来了。比如我按照猜XML中的格式把数据变成车名,其它源代码不用改,运行效果:
以上就是使用自定义控件完成代码复用的目的,关于自定义控件的更多功用需要我们继续探索!如想简单了解,可以阅读之前写过的两篇博文:Android自定义控件系列案例【一】、Android自定义控件系列案例【二】