上篇文章對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();
}
}
}
至此重構完成。