上篇文章对listView 分组和字母索引导航的实现思路做了分析,并依照思路一步步实现,到最后已经较好的实现了全部功能。但是仔细研究就会发现其实现不够好,主要问题:
1. 对于一个使用范围比较广泛的布局,以上实现不够通用,尤其是Bo中需加上一些多余的字段,这些字字段本身并没有意义。
2. 代码都糅合在activity中。
针对以上两点做一些代码重构。首先我们把其优化为一个通用的activity.这样做成通用的View就很容易;然后对代码进行抽取和重构。
想法和思路
以往代码的一个主要问题就是“污染”原有的Bo,而污染的主要原因是需要用这些附加的字段来进行数据处理和生成列表分组标签的时候使用。
原有代码如下:
public class TestBo { /** * 主要字段 */ private String boStr = null; /** * bo拼音缓存 */ private String boPinYin = null; /** * Bo标签标记 */ private String boTagFlag = null; public TestBo() { super(); } public TestBo(String str) { super(); this.boStr = str; } public String getBoStr() { return boStr; } public void setBoStr(String boStr) { this.boStr = boStr; } public String getSortStrPinyin() { return boPinYin; } public void setSortStrPinYin(String pinyin) { this.boPinYin = pinyin; } public void setTag(String tag) { this.boTagFlag = tag; } public String getTag() { return boTagFlag; } }
其实以上Bo中真正有用的有主要字段,其他均为附加字段,其实生成列表只要要求Bo提供按照哪个字段分组就行了。
自然而然的我们就想到了接口,只要实现了相应的接口,接口方法返回需要“分组排序的值”。
数据处理做相应改变即可。
重构BO-接口
首先抽出以下接口:
public interface BoSort { /** * @date 2014-9-3 * @Description: 获取索引的字符串 * @param * @return String */ public String getSortStr(); /** * @date 2014-9-3 * @Description: 获取索引字符串的拼音,这个最好可以有缓存 * @param * @return String */ public String getSortStrPinyin(); /** * @date 2014-9-3 * @Description: * @param * @return void */ public void setSortStrPinYin(String pinyin); /** * @date 2014-9-3 * @Description: 设置标签,需要缓存 * @param * @return void */ public void setTag(String tag); /** * @date 2014-9-3 * @Description: 获取标签,如果为null,说明不是标签 * @param * @return String */ public String getTag(); }
相应Bo实现以上接口即可。但是我们可以提供一个默认实现,这还是一个抽象类,Bo只要继承这个默认实现,并实现为实现的方法public String getSortStr();
/** * @date 2014-9-3 * @Description: 只需实现 getSortStr 其他不要修改 */ public abstract class DefaultBoSortImp implements BoSort{ /** * bo拼音缓存 */ private String boPinYin = null; /** * Bo标签标记 */ private String boTagFlag = null; /** * 一定要有这个构造函数 */ public DefaultBoSortImp() { super(); } @Override public String getSortStrPinyin() { return boPinYin; } @Override public void setSortStrPinYin(String pinyin) { this.boPinYin = pinyin; } @Override public void setTag(String tag) { this.boTagFlag = tag; } @Override public String getTag() { return boTagFlag; } }
数据处理
整体的实现过程和以前类似,数据处理的时候稍微有些改变。我们把数据处理单独抽为一个类,可见处理的过程中,生成分组标签的时候,采用反射,且此数据处理只依赖与接口,而不是具体的Bo,降低了耦合。
public class RulerUtil { /** * 列表适配?? */ public static final String[] indexStr = { "#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; public static final char[] letters = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; /** * @throws IllegalAccessException * @throws InstantiationException * @return返回处理后的数据 * @Description:处理数据,排序,添加标签 */ public static ArrayList<BoSort> genSortedDataAndTagLocation(List<? extends BoSort> myData, HashMap<String, Integer> tagLocation) throws InstantiationException, IllegalAccessException { ArrayList<BoSort> res = new ArrayList<BoSort>(); res.addAll(myData); //首先排序 Collections.sort(res, new Comparator<BoSort>() { @Override public int compare(BoSort lhs, BoSort rhs) { char firChar = checkAndGetPinyin(lhs); char secChar = checkAndGetPinyin(rhs); if (firChar < secChar) { return -1; } else if (firChar > secChar) { return 1; } else return 0; } }); int size = res.size(); int i = 0; char nowTag = '\0'; for (i = 0; i < size; i++) { BoSort temp = res.get(i); char tempTag = checkAndGetPinyin(temp); if(Arrays.binarySearch(letters, tempTag) < 0){ tempTag = '#'; } if (nowTag != tempTag) { //反射生成标签 Class<? extends BoSort> boClass = temp.getClass(); BoSort tagBO = boClass.newInstance(); tagBO.setTag(tempTag+""); res.add(i, tagBO); tagLocation.put(tempTag + "", i); i++; size++; nowTag = tempTag; } } tagLocation.put("#", 0); return res; } private static char checkAndGetPinyin(BoSort bo){ String pinyinStr = bo.getSortStrPinyin(); if (pinyinStr==null) { bo.setSortStrPinYin(HanziToPinyin.getPinYin(bo.getSortStr()).toUpperCase()); pinyinStr = bo.getSortStrPinyin(); } if(pinyinStr!=null&&pinyinStr.length()>=1){ return pinyinStr.charAt(0); } return '\0'; } }
Adaptor实现
Adptor的实现和之前一样,只是adaptor也是只依赖于接口,不依赖于具体的Bo。
Activity的重构
构造一个通用的抽象activity。当需要分组导航的话,只需要继承之,并实现其中的返回数据的方法即可。
首先把右边的字母索引抽出来,做成一个View.
1. RulerWidget
单独的View,可以直接在xml布局中使用。
/** * @Description: 右边尺子导航,需要调用 setOnRulerTouch方法设置回调接口 */ public class RulerWidget extends LinearLayout{ private static final int INDEX_LENGTH = RulerUtil.indexStr.length; public RulerWidget(Context context) { super(context); init(); } public RulerWidget(Context context, AttributeSet attrs) { super(context, attrs); init(); } public RulerWidget(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init(){ int color = getResources().getColor(R.color.g_ruler_letter_color); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_HORIZONTAL; this.bringToFront(); params.weight = 1; for (int i = 0; i < RulerUtil.indexStr.length; i++) { final TextView tv = new TextView(getContext()); tv.setLayoutParams(params); tv.setTextColor(color); tv.setGravity(Gravity.CENTER); tv.setText(RulerUtil.indexStr[i]); this.addView(tv); } this.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int height = v.getHeight(); float pos = event.getY(); int sectionPosition = (int) ((pos / height) / (1f /INDEX_LENGTH)); if (sectionPosition < 0) { sectionPosition = 0; } else if (sectionPosition > INDEX_LENGTH-1) { sectionPosition = INDEX_LENGTH-1; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (onRulerTouch!=null) { onRulerTouch.onDown(sectionPosition); } RulerWidget.this.setBackgroundResource(R.color.g_ruler_selected); break; case MotionEvent.ACTION_MOVE: if (onRulerTouch!=null) { onRulerTouch.onMove(sectionPosition); } break; default: if (onRulerTouch!=null) { onRulerTouch.onOthers(); } RulerWidget.this.setBackgroundResource(R.color.g_blank); } return true; } }); } /** * 回调 */ private OnRulerTouch onRulerTouch; public void setOnRulerTouch(OnRulerTouch onRulerTouch) { this.onRulerTouch = onRulerTouch; } } /** * @date 2014-9-3 * @Description: ruler触摸回调 */ public interface OnRulerTouch{ public void onDown(int position); public void onMove(int position); public void onUP(); public void onOthers(); }
2. Activity
一个抽象的activity. 布局和以前类似。不在贴。
/** * @date 2014-9-3 * @Description:需要实现这个获取数据的方法 * public abstract List<? extends BoSort> getDataList(); */ public abstract class RulerActivity extends Activity{ protected TextView noDataView; protected TextView RulerTag; protected ProgressBarWithText progress; protected ListView listView; protected RulerWidget ruler; private RulerAdapter rulerAdapter; private List<? extends BoSort> originalList; private List<BoSort> dealedList; private HashMap<String, Integer> tagLocation = new HashMap<String, Integer>(); /** * */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.g_ruler); findView(); initView(); initData(); } private void findView() { noDataView = (TextView) findViewById(R.id.g_base_list_nodata); RulerTag = (TextView) findViewById(R.id.g_ruler_tag); progress = (ProgressBarWithText) findViewById(R.id.g_base_progressbar_withtext); listView = (ListView) findViewById(R.id.g_base_list); ruler = (RulerWidget) findViewById(R.id.g_ruler); } private void initView() { progress.setVisibility(View.VISIBLE); RulerTag.setVisibility(View.GONE); noDataView.setVisibility(View.GONE); listView.setVisibility(View.GONE); ruler.setVisibility(View.GONE); } private void initData() { new GetDataAsyTask().execute(); } /** * @date 2014-9-4 * @Description: 需要实现这个获取数据的方法 * @param * @return List<? extends BoSort> */ public abstract List<? extends BoSort> getDataList(); /** * @date 2014-9-3 * @Description: * @param * @return void */ private void handleSuccessData() { listView.setVisibility(View.VISIBLE); ruler.setVisibility(View.VISIBLE); rulerAdapter = new RulerAdapter(dealedList, this); ruler.setOnRulerTouch(new OnRulerTouch() { @Override public void onUP() { } @Override public void onOthers() { RulerTag.setVisibility(View.GONE); } @Override public void onMove(int position) { RulerTag.setText(RulerUtil.indexStr[position]); listView.setSelection(getPosition(position)); } @Override public void onDown(int position) { RulerTag.setVisibility(View.VISIBLE); RulerTag.setText(RulerUtil.indexStr[position]); listView.setSelection(getPosition(position)); } }); listView.setAdapter(rulerAdapter); rulerAdapter.notifyDataSetChanged(); } /** * @Description: 获取触摸字母导航的时候,列表要滚动到的位置。如果触摸的字母,在标签tagLocation 映射中,不存,则向前寻找。 */ private Integer getPosition(final int j) { Integer pos = null; int i = j; while (pos == null && i <= RulerUtil.indexStr.length - 1) { pos = tagLocation.get(RulerUtil.indexStr[i]); i++; } if (pos == null) { pos = dealedList.size() - 1; } return pos; } class GetDataAsyTask extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); progress.setVisibility(View.VISIBLE); } @Override protected Void doInBackground(Void... params) { originalList = getDataList(); try { dealedList = RulerUtil.genSortedDataAndTagLocation(originalList, tagLocation); } catch (Exception e) { e.printStackTrace(); if (dealedList!=null) { dealedList.clear(); dealedList = null; } if (originalList!=null) { originalList.clear(); originalList = null; } if (tagLocation!=null) { tagLocation.clear(); tagLocation = null; } } return null; } @Override protected void onPostExecute(Void result) { progress.setVisibility(View.GONE); super.onPostExecute(result); if(dealedList==null){ noDataView.setVisibility(View.VISIBLE); return; } handleSuccessData(); } } }
至此重构完成。