編輯:關於Android編程
偶爾看到之前寫過的代碼,感覺好多東西幾乎在很多項目中都要用到,雖然每個項目的需求和設計都不同,不過實現的效果都是一樣的,可能只是數據格式和一些顏色等的細微差距.但是有的時候因為一個小改變,就要去重復的修改代碼,麻煩不說,也容易導致新的問題和BUG.
就拿忽然想到的索引欄來說,幾乎寫過的項目中都用到了,比如城市選擇、聯系人等等.這些地方全都需要用到索引欄,但是用法都是一樣的.翻看了幾處之前寫過的代碼,發現每次用到索引欄,都要重新去寫方法來處理數據或者對數據的索引進行提取這些,做法也都大同小異.於是乎,嘗試著重構一下這部分,也方便之後的使用.
先看一下效果圖:
實現
索引欄的實現,網上有很多例子,也比較簡單,就不做過多解釋.因為在不同項目中可能涉及到索引欄字體顏色、大小不同等問題,所以把之前用到的代碼做一下修改,提取出一些自定義屬性,方便修改,就不必每次都去代碼中修改,也避免影響到其他人的使用.直接看一下代碼,在attr中定義一些自定義屬性,如下:
attr:
<?xml version="1.0" encoding="utf-8"?> <resources> <!--SideBar相關--> <!--普通時的顏色--> <attr name="normalColor" format="color"/> <!--選中時的顏色--> <attr name="chooseColor" format="color"/> <!--普通時的背景圖--> <attr name="normalBackground" format="reference"/> <!--選中時的背景圖--> <attr name="chooseBackground" format="reference"/> <!--索引欄文字大小--> <attr name="sideTextSize" format="dimension"/> <declare-styleable name="SideBar"> <attr name="normalColor"/> <attr name="chooseColor"/> <attr name="normalBackground"/> <attr name="chooseBackground"/> <attr name="sideTextSize"/> </declare-styleable> </resources>
把顏色文字大小等屬性提取出來方便修改.然後看一下SideBar
SideBar:
package com.example.junweiliu.commindexdemo.widget; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import com.example.junweiliu.commindexdemo.R; /** * Created by junweiliu on 16/11/24. */ public class SideBar extends View { /** * 點擊回調 */ private OnTouchingLetterChangedListener onTouchingLetterChangedListener; /** * 26字母 */ public static String[] letterStrs = {"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", "#"}; /** * 當前是否選中 */ private int choose = -1; /** * 字母畫筆 */ private Paint paint = new Paint(); /** * 顯示的TextView */ private TextView mTextDialog; /** * 普通時的顏色 */ private int normalColor; /** * 選中的顏色 */ private int chooseColor; /** * 普通時的背景 */ private Drawable normalBackground; /** * 選中時的背景 */ private Drawable chooseBackground; /** * 文字大小 */ private float textSize; /** * 邊框 */ private Rect mRect; public SideBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SideBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // 獲取自定義屬性 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SideBar); normalColor = ta.getColor(R.styleable.SideBar_normalColor, Color.GRAY); chooseColor = ta.getColor(R.styleable.SideBar_chooseColor, Color.RED); normalBackground = ta.getDrawable(R.styleable.SideBar_normalBackground); chooseBackground = ta.getDrawable(R.styleable.SideBar_chooseBackground); textSize = ta.getDimension(R.styleable.SideBar_sideTextSize, TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 13, getResources().getDisplayMetrics())); ta.recycle(); init(); } /** * 為SideBar設置顯示字母的TextView * * @param mTextDialog */ public void setTextView(TextView mTextDialog) { this.mTextDialog = mTextDialog; } /** * 設置 * * @param letter */ public void setLetter(String[] letter) { this.letterStrs = letter; invalidate(); requestLayout(); } /** * 初始化參數 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void init() { paint.setColor(normalColor); paint.setTypeface(Typeface.DEFAULT_BOLD); paint.setAntiAlias(true); paint.setTextSize(textSize); // 獲取單個繪制的rect,用於獲取單個繪制項的高度 mRect = new Rect(); paint.getTextBounds("A", 0, "A".length(), mRect); } /** * 繪制 * * @param canvas */ protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 獲取焦點改變背景顏色. int height = getHeight() - getPaddingTop() - getPaddingBottom();// 獲取對應高度 int width = getWidth(); // 獲取對應寬度 int singleHeight = height / letterStrs.length;// 獲取每一個字母的高度 for (int i = 0; i < letterStrs.length; i++) { // 選中的狀態 if (i == choose) { paint.setColor(chooseColor); paint.setFakeBoldText(true); } // x坐標等於中間-字符串寬度的一半. float xPos = width / 2 - paint.measureText(letterStrs[i]) / 2; float yPos = singleHeight * i + singleHeight; canvas.drawText(letterStrs[i], xPos, yPos, paint); paint.reset();// 重置畫筆 init(); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public boolean dispatchTouchEvent(MotionEvent event) { final int action = event.getAction(); // 點擊的y坐標 final float y = event.getY(); final int oldChoose = choose; final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener; // 獲取當前點擊的字母位置,點擊位置的y坐標比上總的高度相當於點擊的位置比上全部位置(c / b.length = y / getHeight()) final int currChoose = (int) (y / getHeight() * letterStrs.length); switch (action) { case MotionEvent.ACTION_UP: // 重置背景色 if (null != normalBackground) { setBackground(normalBackground); } else { setBackgroundColor(Color.argb(0, 0, 0, 0)); } // 抬起時置為-1 choose = -1; invalidate(); if (mTextDialog != null) { mTextDialog.setVisibility(View.INVISIBLE); } break; default: // 設置背景色 if (null != chooseBackground) { setBackground(chooseBackground); } if (oldChoose != currChoose) { if (currChoose >= 0 && currChoose < letterStrs.length) { if (null != listener) { listener.onTouchingLetterChanged(letterStrs[currChoose]); } if (null != mTextDialog) { mTextDialog.setText(letterStrs[currChoose]); mTextDialog.setVisibility(View.VISIBLE); } // 設置選中的位置為當前位置 choose = currChoose; invalidate(); } } break; } return true; } /** * 向外公開的方法 * * @param onTouchingLetterChangedListener */ public void setOnTouchingLetterChangedListener( OnTouchingLetterChangedListener onTouchingLetterChangedListener) { this.onTouchingLetterChangedListener = onTouchingLetterChangedListener; } /** * 回調接口 * * @author coder */ public interface OnTouchingLetterChangedListener { void onTouchingLetterChanged(String s); } /** * 測量 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); // 當高度為自適應時,高度為字母高度*字母數量*2 即間隔為單位高度 int wrapHeight = letterStrs.length * (mRect.height() * 2); // 當寬度為自適應使,寬度為字母寬度*2 int warpWidth = mRect.width() * 2; setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth : warpWidth , (heightMode == MeasureSpec.EXACTLY) ? sizeHeight //wrap_content時的高度 : wrapHeight); } }
很簡單,只是提取出來了一些自定義屬性,沒什麼可說的,接下來分析一下如何讓索引欄變得通用,先來想一下索引欄一般的寫法.首先拿到一個數據源,然後對這個數據源進行處理,從數據源中提取出首字母當做索引(當然數據源中可能已經含有首字母或者索引等字段),有了索引之後,再在適配器中進行判斷來控制是否顯示索引標題(我的做法是判斷第一次出現當前索引的數據源位置和當前位置是否相同,如果相同則顯示索引標題),處理完索引標題的顯示和隱藏,最後就是跟索引欄進行綁定(實現索引欄的回調方法並做相關處理).大體步驟就是這樣,接下來就是找一下處理不同的地方,比對了一下,發現問題基本都是出現在數據格式不同上,有的數據的索引字段可能叫Letter,有的可能叫LetterName,這就導致了每次對這些數據進行處理時,都要重新寫方法或者修改方法,使得這些方法不共用.那怎麼解決一下這個問題呢,最開始想到的是寫一個抽象類,然後用一個抽象方法getLetterName()來約束索引.每個需要用到索引欄的Bean都去繼承這個抽象類,重寫這個抽象方法,從而達到統一約束索引值的效果,也就解決了索引值字段不同的問題,這樣就可以用一個公共的方法來處理不同的數據源.後來又考慮了一下,這個地方其實用接口會更加合適一點,接口靈活性更大,而且也是面向接口編程的一種體現.分析了這麼多,來具體代碼實現一下,提出一個接口,之後所有需要用到索引的數據Bean去實現這個接口中的getLetterName()方法並且重寫這個方法來返回索引值即可.
接口SideBase:
package com.example.junweiliu.commindexdemo.bean; /** * Created by junweiliu on 16/11/21. */ public interface SideBase { String getLetterName(); }
然後數據Bean去實現這個接口,例如比較常見的CityBean:
package com.example.junweiliu.commindexdemo.bean; public class CityBean implements SideBase { /** * 城市名 */ private String cityName; /** * 首字母 */ private String cityHeader; /** * 城市信息 */ private String cityMes; public CityBean(String cityName, String cityHeader, String cityMes) { this.cityName = cityName; this.cityHeader = cityHeader; this.cityMes = cityMes; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } public String getCityHeader() { return cityHeader; } public void setCityHeader(String cityHeader) { this.cityHeader = cityHeader; } public String getCityMes() { return cityMes; } public void setCityMes(String cityMes) { this.cityMes = cityMes; } /** * 獲取索引 * * @return */ @Override public String getLetterName() { return cityHeader; } }
在getLetterName()方法中去返回索引值.
接下來就可以去寫一些公共的處理方法.比如
/** * 根據當前選中的項獲取其第一次出現該項首字母的位置 * * @param position 當前選中的位置 * @param datas 數據源 * @return */ public static int getPositionForSection(int position, List<? extends SideBase> datas) { // 當前選中的項 SideBase sideBase = datas.get(position); for (int i = 0; i < datas.size(); i++) { String firstStr = datas.get(i).getLetterName().toUpperCase(); // 返回第一次出現該項首字母的位置 if (firstStr.equals(sideBase.getLetterName())) { return i; } } return -1; }
因為使用的接口,這裡就可以用通配符?的方式來對數據進行處理,只關心和處理getLetterName()方法即可.還可以做其他處理:
/** * 獲取所選中的索引在列表中的位置 * * @param list * @param letter * @return */ public static int getLetterPosition(List<? extends SideBase> list, String letter) { int position = -1; if (list != null && !list.isEmpty() && !"".equals(letter)) { for (int i = 0; i < list.size(); i++) { SideBase bean = list.get(i); if (bean.getLetterName().equals(letter)) { position = i; break; } } } return position; } /** * 篩選出數據源中所包含的全部索引值 * * @param list * @return */ public static String[] getLetters(List<? extends SideBase> list) { List<String> letters = new ArrayList<>(); if (list != null && !list.isEmpty()) { for (int i = 0; i < list.size(); i++) { if (!letters.contains(list.get(i).getLetterName())) { letters.add(list.get(i).getLetterName()); } } } return (String[]) letters.toArray(new String[letters.size()]); }
通過分析和重構之後,這些之前不能公用的方法就變得通用起來,很方便,之後用起來也會特別簡單、舒心.
完整代碼
公共處理類
CommUtil:
package com.example.junweiliu.commindexdemo.util; import com.example.junweiliu.commindexdemo.bean.SideBase; import java.util.ArrayList; import java.util.List; /** * Created by junweiliu on 16/11/24. */ public class CommUtil { /** * 根據當前選中的項獲取其第一次出現該項首字母的位置 * * @param position 當前選中的位置 * @param datas 數據源 * @return */ public static int getPositionForSection(int position, List<? extends SideBase> datas) { // 當前選中的項 SideBase sideBase = datas.get(position); for (int i = 0; i < datas.size(); i++) { String firstStr = datas.get(i).getLetterName().toUpperCase(); // 返回第一次出現該項首字母的位置 if (firstStr.equals(sideBase.getLetterName())) { return i; } } return -1; } /** * 獲取所選中的索引在列表中的位置 * * @param list * @param letter * @return */ public static int getLetterPosition(List<? extends SideBase> list, String letter) { int position = -1; if (list != null && !list.isEmpty() && !"".equals(letter)) { for (int i = 0; i < list.size(); i++) { SideBase bean = list.get(i); if (bean.getLetterName().equals(letter)) { position = i; break; } } } return position; } /** * 篩選出數據源中所包含的全部索引值 * * @param list * @return */ public static String[] getLetters(List<? extends SideBase> list) { List<String> letters = new ArrayList<>(); if (list != null && !list.isEmpty()) { for (int i = 0; i < list.size(); i++) { if (!letters.contains(list.get(i).getLetterName())) { letters.add(list.get(i).getLetterName()); } } } return (String[]) letters.toArray(new String[letters.size()]); } }
適配器CityAdapter:
package com.example.junweiliu.commindexdemo.adapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import com.example.junweiliu.commindexdemo.R; import com.example.junweiliu.commindexdemo.bean.CityBean; import com.example.junweiliu.commindexdemo.util.CommUtil; import java.util.List; /** * Created by junweiliu on 16/11/24. */ public class CityAdapter extends BaseAdapter { /** * 上下文 */ private Context context; /** * 布局加載器 */ private LayoutInflater mInflater; /** * 數據源 */ private List<CityBean> cityBeanList; /** * 構造方法 * * @param context * @param cityBeanList */ public CityAdapter(Context context, List<CityBean> cityBeanList) { this.context = context; this.cityBeanList = cityBeanList; } @Override public int getCount() { return cityBeanList.size(); } @Override public Object getItem(int i) { return cityBeanList.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int position, View convertView, ViewGroup viewGroup) { ViewHolder viewHolder = null; CityBean bean = cityBeanList.get(position); if (convertView == null) { convertView = mInflater.from(context).inflate(R.layout.item_city, null); viewHolder = new ViewHolder(); viewHolder.headerTv = (TextView) convertView.findViewById(R.id.tv_item_citys_header); viewHolder.contentTv = (TextView) convertView.findViewById(R.id.tv_item_citys_context); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } // 如果當前位置為第一次出現該類首字母的位置,則顯示headerTv if (position == CommUtil.getPositionForSection(position, cityBeanList)) { viewHolder.contentTv.setVisibility(View.VISIBLE); viewHolder.headerTv.setVisibility(View.VISIBLE); viewHolder.headerTv.setText(bean.getLetterName()); } else { viewHolder.headerTv.setVisibility(View.GONE); viewHolder.contentTv.setVisibility(View.VISIBLE); } viewHolder.contentTv.setText(bean.getCityName()); return convertView; } /** * vh */ class ViewHolder { TextView headerTv; TextView contentTv; } }
MainActivity:
package com.example.junweiliu.commindexdemo; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; import android.widget.TextView; import com.example.junweiliu.commindexdemo.adapter.CityAdapter; import com.example.junweiliu.commindexdemo.bean.CityBean; import com.example.junweiliu.commindexdemo.util.CommUtil; import com.example.junweiliu.commindexdemo.widget.SideBar; import java.util.ArrayList; import java.util.List; public class MainActivity extends Activity { /** * 城市列表數據 */ private List<CityBean> cityBeanList = new ArrayList<>(); /** * 城市lv */ private ListView cityList; /** * 索引欄 */ private SideBar mSideBar; /** * 顯示的tv */ private TextView mShowTv; /** * 適配器 */ private CityAdapter mCityAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); } /** * 初始化數據 */ private void initData() { CityBean bean1 = new CityBean("安徽", "A", "安徽故事"); CityBean bean2 = new CityBean("安徽1", "A", "安徽1故事"); CityBean bean3 = new CityBean("安徽2", "A", "安徽2故事"); CityBean bean4 = new CityBean("北京", "B", "北京故事"); CityBean bean5 = new CityBean("北京1", "B", "北京1故事"); CityBean bean6 = new CityBean("北京2", "B", "北京2故事"); CityBean bean7 = new CityBean("重慶", "C", "重慶故事"); CityBean bean8 = new CityBean("重慶1", "C", "重慶1故事"); CityBean bean9 = new CityBean("重慶2", "C", "重慶2故事"); CityBean bean10 = new CityBean("貴州", "G", "貴州故事"); CityBean bean11 = new CityBean("貴州1", "G", "貴州2故事"); CityBean bean12 = new CityBean("貴州2", "G", "貴州3故事"); CityBean bean13 = new CityBean("天津", "T", "天津故事"); CityBean bean14 = new CityBean("天津1", "T", "天津1故事"); CityBean bean15 = new CityBean("天津2", "T", "天津2故事"); cityBeanList.add(bean1); cityBeanList.add(bean2); cityBeanList.add(bean3); cityBeanList.add(bean1); cityBeanList.add(bean2); cityBeanList.add(bean3); cityBeanList.add(bean4); cityBeanList.add(bean5); cityBeanList.add(bean6); cityBeanList.add(bean4); cityBeanList.add(bean5); cityBeanList.add(bean6); cityBeanList.add(bean7); cityBeanList.add(bean8); cityBeanList.add(bean9); cityBeanList.add(bean7); cityBeanList.add(bean8); cityBeanList.add(bean9); cityBeanList.add(bean10); cityBeanList.add(bean11); cityBeanList.add(bean12); cityBeanList.add(bean10); cityBeanList.add(bean11); cityBeanList.add(bean12); cityBeanList.add(bean13); cityBeanList.add(bean14); cityBeanList.add(bean15); cityBeanList.add(bean13); cityBeanList.add(bean14); cityBeanList.add(bean15); } /** * 初始化控件 */ private void initView() { cityList = (ListView) findViewById(R.id.lv_city); mSideBar = (SideBar) findViewById(R.id.sb_city); mShowTv = (TextView) findViewById(R.id.tv_city_show); mCityAdapter = new CityAdapter(MainActivity.this, cityBeanList); cityList.setAdapter(mCityAdapter); // 設置需要顯示的索引欄內容 mSideBar.setLetter(CommUtil.getLetters(cityBeanList)); // 設置需要顯示的提示框 mSideBar.setTextView(mShowTv); mSideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() { @Override public void onTouchingLetterChanged(String s) { int position = CommUtil.getLetterPosition(cityBeanList, s); if (position != -1) { cityList.setSelection(position); } } }); } }
布局文件activity_main:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.junweiliu.commindexdemo.MainActivity"> <!--城市列表--> <ListView android:id="@+id/lv_city" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> <!--索引欄--> <com.example.junweiliu.commindexdemo.widget.SideBar android:id="@+id/sb_city" android:layout_width="wrap_content" android:layout_height="200dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" app:sideTextSize="13sp"/> <!--顯示的字母--> <TextView android:id="@+id/tv_city_show" android:layout_width="80dp" android:layout_height="80dp" android:layout_centerInParent="true" android:background="#3F51B5" android:gravity="center" android:textColor="#ffffff" android:textSize="25sp" android:visibility="gone" /> </RelativeLayout>
適配器布局item_city:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- 索引頭 --> <TextView android:id="@+id/tv_item_citys_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#E3E3E3" android:padding="15dp" android:text="A" android:textColor="#666666" android:textSize="14sp" android:visibility="gone"/> <!-- 內容 --> <TextView android:id="@+id/tv_item_citys_context" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff" android:padding="15dp" android:textColor="#000000" android:textSize="14sp" android:visibility="gone"/> </LinearLayout>
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
在Android開發過程中,經常會碰到Activity之間的切換效果的問題,下面介紹一下如何實現左右滑動的切換效果,首先了解一下Activity切換的實現,從Androi
之前的博文中有介紹關於圖片輪播的實現方式,分別為(含超鏈接):1、《Android中使用ViewFlipper實現屏幕切換》2、《Android中使用ViewPager實
預覽效果圖:需要權限:<uses-permission android:name=com.android.launcher.permission.INSTALL_S
分析android Activity啟動流程中ActivityManagerService所扮演的角色一、概述上一篇文章startActivit