本文介绍Launcher中的Apps字母索引功能,什么是字母索引呢?
Google的原生的Launcher是用抽屉来装所有App的,但是抽屉真的方便吗?最近市场上流行的好多轻量级桌面都纷纷抛弃了抽屉,用Apps索引来代替。
如图:
我们来分析一下然后自己做一个
1.整体可以重写RelativeLayout
2.右边的滑动的字母条可以重写LinearLayout
3.中间装App的用ListView,并重写一个Item
4.Item重写LinearLayout
如下:
1.AppsCustomsizeIndexView.java 整体组件
初始化Listiew、重写BaseAdapter
初始化自定义的AppsIndexLayout 、初始化时获取所有APP的title字段,生成字母List,里面不能有重复,然后排序,准备在AppsIndexLayout中使用。
XML:
<?xml version="1.0" encoding="utf-8"?>
<com.ola.launcher.AppsCustomizeIndexView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.ola.launcher"
android:id="@+id/apps_customize_indexview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible"
launcher:itemIconHeight="50dp"
launcher:itemIconWidth="50dp" >//这两个属性暂时没有用,可以不加
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:textSize="50dp" />
<ListView
android:id="@+id/apps_customize_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/textview"
android:layout_toLeftOf="@+id/apps_customize_indexlayout"
android:background="@null"
android:divider="@null"
android:scrollbars="none" >//中间装app的ListView
</ListView>
<com.ola.launcher.AppsIndexLayout
android:id="@+id/apps_customize_indexlayout"
android:layout_width="@dimen/apps_custom_indexview_width"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_below="@+id/textview"
android:layout_marginBottom="@dimen/apps_custom_indexview_marginbottom"
android:background="@android:color/holo_blue_dark" >
</com.ola.launcher.AppsIndexLayout>//右边的字母条
</com.ola.launcher.AppsCustomizeIndexView>
代码:
public class AppsCustomizeIndexView extends RelativeLayout implements ScrollListener {
private TextView mTextView;
private AppsIndexLayout mIndexLayout;//字母条
private ListView mListView;
private AppsAdapter mAppsAdapter;
private ArrayList<String> mKeys = new ArrayList<String>();//字母条需要的数据
private HashMap<String, ArrayList<ApplicationInfo>> mKeyItem = new HashMap<String, ArrayList<ApplicationInfo>>();//app数据
private ArrayList<ApplicationInfo> mIndexApps = new ArrayList<ApplicationInfo>();//零时装app的List
private ArrayList<ApplicationInfo> mApplicationInfos;
String mEmpty = "";
String mRencent = "⊙";
String mOther = "#";
public AppsCustomizeIndexView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AppsCustomizeIndexView(Context context) {
super(context);
}
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
mIndexLayout = (AppsIndexLayout) findViewById(R.id.apps_customize_indexlayout);
mListView = (ListView) findViewById(R.id.apps_customize_listview);
mIndexLayout.setOnScrollListener(this);
mTextView = (TextView) findViewById(R.id.textview);
}
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(MotionEvent event) {
return true;
}
private void setTestText(String s) {
mTextView.setText(s);
}
public void setDate(ArrayList<ApplicationInfo> infos) {
this.mApplicationInfos = infos;
setTestText(mApplicationInfos.size() + "");
mKeys = getKeyFromApps();
mIndexLayout.setDate(mKeys);
mAppsAdapter = new AppsAdapter(mKeys, mKeyItem, getContext());
mListView.setAdapter(mAppsAdapter);
}
@SuppressLint("DefaultLocale")//通过遍历所有AppsList的App的title字段,取出第一个字符,生成一个List
private ArrayList<String> getKeyFromApps() {
ArrayList<String> mIndexKey = new ArrayList<String>();
String language = Locale.getDefault().getLanguage();
Pattern pattern = Pattern.compile("^[A-Za-z]+$");
for (int i = 0; i < mApplicationInfos.size(); i++) {
String key = mApplicationInfos.get(i).title.toString().trim().toUpperCase().charAt(0) + mEmpty;
if (pattern.matcher(key).matches()) {//匹配大小写英文字母
if (!mIndexKey.contains(key)) {
mIndexKey.add(key);
mIndexApps = new ArrayList<ApplicationInfo>();
mKeyItem.put(key, mIndexApps);
} else {
mIndexApps = mKeyItem.get(key);
}
if (mIndexApps != null) {
mIndexApps.add(mApplicationInfos.get(i));
}
} else {
if (Locale.CHINA.getLanguage().equalsIgnoreCase(language)) {
char c = key.toCharArray()[0];
if (Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS.equals(Character.UnicodeBlock.of(c))) {
//中文时候的处理、还有其他语言考虑
} else {
addOther(mIndexKey, mKeyItem, mApplicationInfos.get(i), mOther);//乱码时候处理
}
} else {
String o = key;
if (!mIndexKey.contains(o)) {
mIndexKey.add(o);
mIndexApps = new ArrayList<ApplicationInfo>();
mKeyItem.put(o, mIndexApps);
} else {
mIndexApps = mKeyItem.get(o);
}
if (mIndexApps != null) {
mIndexApps.add(mApplicationInfos.get(i));
}
}
}
}
// mIndexKey.add(mRencent);
// mIndexKey.add(mOther);
Collections.sort(mIndexKey);
addOther(mIndexKey, mKeyItem, mApplicationInfos.get(0), mOther);
addOther(mIndexKey, mKeyItem, mApplicationInfos.get(0), mRencent);
return mIndexKey;
}
@Override
public void onDrag(String key) {
mListView.setSelection(findPosFromList(key));
}
private void addOther(ArrayList<String> keystrings, HashMap<String, ArrayList<ApplicationInfo>> keyitem,
ApplicationInfo apps, String string) {
ArrayList<ApplicationInfo> mInfos;
if (!keystrings.contains(string)) {
keystrings.add(0, string);
mInfos = new ArrayList<ApplicationInfo>();
keyitem.put(string, mInfos);
} else {
mInfos = keyitem.get(string);
}
if (mInfos != null) {
mInfos.add(apps);
}
}
public int findPosFromList(String key) {
for (int i = 0; i < mKeys.size(); i++) {
if (key.equals(mKeys.get(i))) {
return i;
}
}
return 0;
}
class AppsAdapter extends BaseAdapter {
private ArrayList<String> stringsArray;
private HashMap<String, ArrayList<ApplicationInfo>> mKeyItem;
Context context;
public AppsAdapter(ArrayList<String> stringsArray, HashMap<String, ArrayList<ApplicationInfo>> mKeyItem, Context context) {
this.stringsArray = stringsArray;
this.context = context;
this.mKeyItem = mKeyItem;
}
@Override
public int getCount() {
return stringsArray.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.apps_customize_indexview_item, null);
}
((AppsCustomizeIndexViewItemLayout) convertView).setKeyString(stringsArray.get(position).toString());
((AppsCustomizeIndexViewItemLayout) convertView).setMeasure(mKeyItem.get(stringsArray.get(position)).size());
((AppsCustomizeIndexViewItemLayout) convertView).setContent(mKeyItem.get(stringsArray.get(position)));
return convertView;
}
}
}
2.AppsIndexLayout.java 右边的字母条组件
我们来分析一下:
仅仅是显示一排字母的话,我们就让他继承LinearLayout,并且设置排列方式为竖直,然后在LinearLayout中添加上若干个TextView就可以,用TextView来显示具体的字母。
再自己定义一个接口ScrollListener,用来在触摸事件中去触发该接口的方法并传递参数,通过该方法去控制另外组件的状态变化
触摸过程中调用接口的方法,并且计算位置,让触摸周围的TexView高亮显示
java:
public class AppsIndexLayout extends LinearLayout {
public ScrollListener mDragListener;
private ArrayList<String> mIndexKey;
private TextView mTextView;
private int mViewHeight;
private Rect mHitRect = new Rect();
private String mColor3 = "#B8B3AE";
private String mColor2 = "#C7C5BD";
private String mColor1 = "#FFFFFF";
public AppsIndexLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(VERTICAL);
setGravity(Gravity.CENTER_HORIZONTAL);
}
// @Override
// protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// int result_w = mearWidth(widthMeasureSpec);
// int result_h = mearHeight(heightMeasureSpec);
//
// setMeasuredDimension(result_w, result_h);
// }
// private int mearWidth(int widthMeasureSpec) {
// int result = 0;
// int specMode = MeasureSpec.getMode(widthMeasureSpec);
// int spacSize = MeasureSpec.getSize(widthMeasureSpec);
// if (specMode == MeasureSpec.EXACTLY) {
// result = spacSize;
// } else {
// result = getResources().getInteger(R.dimen.apps_custom_indexview_width);
// if (specMode == MeasureSpec.AT_MOST) {
// result = Math.min(result, spacSize);
// }
// }
// return result;
// }
// private int mearHeight(int heightMeasureSpec) {
// int result = 0;
// int specMode = MeasureSpec.getMode(heightMeasureSpec);
// int spacSize = MeasureSpec.getSize(heightMeasureSpec);
// if (specMode == MeasureSpec.EXACTLY) {
// result = spacSize;
// } else {
// result = getResources().getDisplayMetrics().heightPixels;
// if (specMode == MeasureSpec.AT_MOST) {
// result = Math.min(result, spacSize);
// }
// }
// mViewHeight = result;
// return result;
// }
public void setDate(ArrayList<String> indexkey) {
int h = getResources().getDisplayMetrics().heightPixels;
mIndexKey = indexkey;
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, h / (indexkey.size() + 6));
for (int i = 0; i < mIndexKey.size(); i++) {
TextView textView = new TextView(getContext());
textView.setGravity(Gravity.CENTER);
textView.setTextSize(12);
textView.setText(mIndexKey.get(i).toString());
addView(textView, layoutParams);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
default:
getKeyString(event);
break;
}
return true;
}
private void getKeyString(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
for (int i = 0; i < getChildCount(); i++) {
if (getViewByEvent(x, y, getChildAt(i))) {
((TextView) getChildAt(i)).setTextColor(Color.parseColor(mColor1));
mDragListener.onDrag(((TextView) getChildAt(i)).getText().toString());//调用接口的方法,参数传递我们当前的TextView的文本
if (i + 1 < getChildCount()) {
((TextView) getChildAt(i + 1)).setTextColor(Color.parseColor(mColor1));//计算触摸时的TextView位置,在越界的情况下让其上下的都高亮显示
for (int j = i + 2; j < getChildCount(); j++) {
((TextView) getChildAt(j)).setTextColor(Color.parseColor(mColor2));
}
}
if (i - 1 > 0) {
((TextView) getChildAt(i - 1)).setTextColor(Color.parseColor(mColor1));
for (int j = 0; j < i - 2; j++) {
((TextView) getChildAt(j)).setTextColor(Color.parseColor(mColor2));
}
}
}
}
}
private boolean getViewByEvent(int x, int y, View v) {
v.getHitRect(mHitRect);
int i = (mHitRect.left + mHitRect.right) / 2;
return mHitRect.contains(i, y);
}
public void addChild(TextView view) {
addView(view);
}
public void setOnScrollListener(ScrollListener dragListener) {
this.mDragListener = dragListener;
}
interface ScrollListener {
public void onDrag(String key);
}
}
3.AppsCustomsizeIndexViewItemLayout.java
ListView中的高矮不一的Item,里面显示大字母的TextView,显示具体App的GridLayout
GridLayout可以设置行列
Xml:apps_customsize_indexview_item
<?xml version="1.0" encoding="utf-8"?>
<com.ola.launcher.AppsCustomizeIndexViewItemLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light" >
<TextView
android:id="@+id/apps_index_item_key"
android:layout_width="@dimen/apps_custom_indexview_key_width"
android:layout_height="@dimen/apps_custom_indexview_key_height"
android:layout_marginLeft="@dimen/apps_custom_indexview_key_marginleft"
android:layout_marginTop="@dimen/apps_custom_indexview_key_margintop"
android:background="@drawable/apps_index_item_key_bg"
android:textColor="@android:color/white"
android:gravity="center"
android:textSize="@dimen/apps_custom_indexview_key_textsize" />
<!-- <GridLayout
android:id="@+id/apps_index_item_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</GridLayout> -->
</com.ola.launcher.AppsCustomizeIndexViewItemLayout>
Java:
public class AppsCustomizeIndexViewItemLayout extends LinearLayout implements OnClickListener {
private TextView mKeyView;
private GridLayout mAppsGridLayout;
private int mGridCellX = 4;
Launcher mLauncher;
private IconCache mIconCache;
public AppsCustomizeIndexViewItemLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mLauncher = (Launcher) context;
mIconCache = ((LauncherApplication) context.getApplicationContext()).getIconCache();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private int onMeasureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = specSize;
}
return result;
}
private int onMeasureHeight(int heightMeasureSpaec) {
int result = 0;
int specMode = MeasureSpec.getMode(heightMeasureSpaec);
int spacSize = MeasureSpec.getSize(heightMeasureSpaec);
if (specMode == MeasureSpec.EXACTLY) {
result = spacSize;
} else {
result = spacSize;
}
return result;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mKeyView = (TextView) findViewById(R.id.apps_index_item_key);
// mAppsGridLayout = (GridLayout)
// findViewById(R.id.apps_index_item_grid);
mAppsGridLayout = new GridLayout(getContext());
mAppsGridLayout.setColumnCount(mGridCellX);
addView(mAppsGridLayout);
}
public void setKeyString(String key) {
mKeyView.setText(key);
}
public void setGridBackground(int c) {
mAppsGridLayout.setBackgroundColor(c);
}
public void setMeasure(int count) {
}
public void setContent(ArrayList<ApplicationInfo> infos) {
mAppsGridLayout.removeAllViews();
IconTextView itemView;
for (int i = 0; i < infos.size(); i++) {
// itemView = (PagedViewIcon)
// LayoutInflater.from(getContext()).inflate(R.layout.apps_customize_application,
// null);
itemView = new IconTextView(getContext());
// itemView.applyFromApplicationInfo(infos.get(i), true, null);
itemView.setIcon(new FastBitmapDrawable(infos.get(i).iconBitmap));
itemView.setText(infos.get(i).title);
// itemView.setOnClickListener(this);
mAppsGridLayout.addView(itemView, Math.min(1, mAppsGridLayout.getChildCount()), new LayoutParams(140, 200));
}
}
@Override
public void onClick(View v) {
ApplicationInfo info = (ApplicationInfo) v.getTag();
startActivity(info, v);
}
public void startActivity(ApplicationInfo info, View v) {
final Intent intent = info.intent;
int[] pos = new int[2];
v.getLocationOnScreen(pos);
intent.setSourceBounds(new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()));
if (!mLauncher.startActivitySafely(v, intent, null)) {
}
}
class IconTextView extends TextView {//显示AppItem View
public IconTextView(Context context) {
super(context);
setGravity(Gravity.CENTER_HORIZONTAL);
setPadding(10, 20, 10, 10);
// setTextColor(Color.BLACK);
}
public void setIcon(Drawable d) {
setCompoundDrawablesWithIntrinsicBounds(null, d, null, null);//TextView上下左右设置图片的,这里我们只给top设置
}
}
}
最后们来看看功能的实现效果: