Launcher中的App索引——Apps字母索引组件
本文介绍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设置 } } }
最后们来看看功能的实现效果: