最近研究了下開發桌面應用,在這裏跟大家分享下,廢話不多說,先來看下基本的效果圖。
應用列表
點擊圖標,打開應用
又是我的華爲5S出鏡,基本實現,列表展示各個應用圖標和應用名稱,點擊後打開對應的應用,至此自己的Android桌面的雛形已經基本具備了,下面開始擼代碼。先看下到這一步的代碼結構。
看了下結構,文件並不多,主頁面我採用的是ViewPager 嵌套 GridView , 每頁固定的數量爲20個圖標,通過左右滑動來進行翻頁操作,項目中還用到了數據庫,用的是郭嬸的Litepal,主要是存放手機已經安裝的應用的信息,信息裏面包括圖標哦,稍後會講到的。下面先看MainActivity.java
看下main_activity.xml的寫法:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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" android:background="@mipmap/bg" tools:context="com.cjt.mylauncher.MainActivity"> <RelativeLayout android:id="@+id/main_layout" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintHorizontal_weight="1" app:layout_constraintVertical_weight="1" android:layout_marginBottom="0dp" android:layout_marginLeft="0dp" android:layout_marginRight="0dp" android:layout_marginTop="25dp" app:layout_constraintBottom_toTopOf="@+id/nav_layout" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"> <android.support.v4.view.ViewPager android:padding="16dp" android:id="@+id/main_pager" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> <RelativeLayout android:id="@+id/nav_layout" android:layout_width="0dp" android:layout_height="80dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" android:padding="5dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"> <android.support.v7.widget.RecyclerView android:id="@+id/nav_bar" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> </android.support.constraint.ConstraintLayout>
是的,你沒有看錯,在主頁面的佈局中,我採用的是約束佈局,其實感覺這個佈局玩熟練了不輸RelativeLayout,這裏也算一中嘗試,大家不妨也試下。另外我在項目中使用了ButterKnife框架,至於這個怎麼使用,大家可以自行百度學習。我也會把我的依賴文件給出來,在這裏的createGrid()這個方法中我是根據傳入的應用實體的數量去動態添加每一頁的GridView佈局的,下面給出gridview佈局文件和相應的Adapter的寫法:
首先是layout_grid_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center" android:layout_width="wrap_content" android:layout_height="match_parent"> <GridView android:id="@+id/app_grid" android:numColumns="4" android:layout_gravity="center" android:horizontalSpacing="25dp" android:verticalSpacing="5dp" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>非常簡單的一個GridView佈局,固定了每行的數量爲4,感覺這個處理不好,後面會根據實際使用做相應的調整,不用GridView了,打算後期改成RecycleView試下效果。
public class GridAppAdapter extends BaseAdapter { List<SysAppBean> sysAppBeanList = new ArrayList<>(); @Override public int getCount() { return sysAppBeanList.size(); } @Override public SysAppBean getItem(int i) { return sysAppBeanList.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int i, View view, ViewGroup viewGroup) { view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_grid_icon , viewGroup ,false); TextView name = ViewHolder.get(view , R.id.app_name); ImageView icon = ViewHolder.get(view , R.id.app_icon); name.setText(getItem(i).getAppName()); icon.setImageBitmap(BitmapUtil.byteToBitmap(getItem(i).getAppIcon())); view.setOnClickListener(new itemClick(viewGroup.getContext() , getItem(i))); return view; } public void notifyData(List<SysAppBean> appBeanList){ this.sysAppBeanList = appBeanList ; this.notifyDataSetChanged(); } class itemClick implements View.OnClickListener { private SysAppBean bean ; private Context context ; public itemClick(Context context, SysAppBean item) { this.context = context ; this.bean = item ; } @Override public void onClick(View view) { AppUtil.openAppByPackageName(context , bean.getPackageName()); } } }
關於Adapter的這種寫法,我之前的博文也有提到,單個itemView設置點擊事件,並且對外暴露一個notifyData()的公共方法,這種寫法其實很節省代碼的,而且避免了很多不必要的參數應用,例如通常的寫法中Adapter會傳入List<E> 和 Context 參數。
這裏在onClick方法中,表示點擊了該圖標,調用的是打開該圖標對應的應用的方法,我把這個方法提到了一個公共類AppUtil.java中,如下
public class AppUtil { /** * 通過指定的包名啓動應用 * @param context 上下文 * @param packageName 指定啓動的包名 */ public static void openAppByPackageName(Context context , String packageName) { Log.d("CJT","openAppByPackageName --00-- "+packageName); if (checkApplication(context , packageName)) { Log.d("CJT","openAppByPackageName --11-- "+packageName); Intent localIntent = new Intent("android.intent.action.MAIN", null); localIntent.addCategory("android.intent.category.LAUNCHER"); List<ResolveInfo> appList = context.getPackageManager().queryIntentActivities(localIntent, 0); for (int i = 0; i < appList.size(); i++) { ResolveInfo resolveInfo = appList.get(i); String packageStr = resolveInfo.activityInfo.packageName; String className = resolveInfo.activityInfo.name; Log.d("CJT","openAppByPackageName --22-- packageName :"+packageName + " -- packageStr : " + packageStr); if (packageStr.equals(packageName)) { Log.d("CJT","openAppByPackageName --7777777777777777-- packageName :"+packageName + " -- packageStr : " + packageStr); // 這個就是你想要的那個Activity ComponentName cn = new ComponentName(packageStr, className); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(cn); context.startActivity(intent); Log.d("CJT" , "openApp-----111---打開完成!!"); } } }else{ Toast.makeText(context , "未安裝此應用" , Toast.LENGTH_LONG).show(); } } /** * 卸載指定應用的包名 * @param context 上下文 * @param packageName 指定的應用包名 */ public static void unInstall(Context context ,String packageName) { if (checkApplication(context , packageName)) { Uri packageURI = Uri.parse("package:" + packageName); Intent intent = new Intent(Intent.ACTION_DELETE); intent.setData(packageURI); context.startActivity(intent); Log.d("CJT" , "unInstall -- 刪除成功!" + packageName); } } /** * 判斷該包名的應用是否安裝 * @param context 上下文 * @param packageName 應用包名 * @return 是否安裝 */ public static boolean checkApplication(Context context, String packageName) { if (packageName == null || "".equals(packageName)) { return false; } try { context.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); return true; } catch (PackageManager.NameNotFoundException e) { return false; } } }其實這個作爲一個公共工具類,我覺得對大家也是比較有用的,有用的話大家可以收藏下,拿走不謝。
實體類SysAppBean.java也比較簡單,該類繼承自DataSupport(不明白的同學可以去學習下郭霖的Litepal)如下:
實體類中圖標的保存使用的byte數組,所以呢順帶給大家一個轉換方法,BitmapUtil.java,這裏轉換的時候可能會有坑,註釋中給大家講明白了。
public class BitmapUtil { public static byte[] drawableToByte(Drawable drawable) { // 第一步,將Drawable對象轉化爲Bitmap對象 , 使用下面的方法,防止VectorDrawable cannot be cast to Drawable int w = drawable.getIntrinsicWidth(); int h = drawable.getIntrinsicHeight(); Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); //注意,下面三行代碼要用到,否則在View或者SurfaceView裏的canvas.drawBitmap會看不到圖 Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, w, h); drawable.draw(canvas); //第二步,聲明並創建一個輸出字節流對象 ByteArrayOutputStream os = new ByteArrayOutputStream(); //第三步,調用compress將Bitmap對象壓縮爲PNG格式,第二個參數爲PNG圖片質量,第三個參數爲接收容器,即輸出字節流os bitmap.compress(Bitmap.CompressFormat.PNG, 100, os); return os.toByteArray(); } public static Drawable byteToDrawable(byte[] bytes) { //第一步,從數據庫中讀取出相應數據,並保存在字節數組中 //第二步,調用BitmapFactory的解碼方法decodeByteArray把字節數組轉換爲Bitmap對象 Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); //第三步,調用BitmapDrawable構造函數生成一個BitmapDrawable對象,該對象繼承Drawable對象,所以在需要處直接使用該對象即可 // Bitmapdrawable bd = new BitmapDrawable(bmp); return new BitmapDrawable(bmp); } public static Bitmap byteToBitmap(byte[] bytes) { return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } }一定要用到創建cavas的三行代碼,否則你會看到很神奇的事情發生,至於是什麼事情,留給有心人自己去見證。這個類提供了bitmap和byte[]互相轉換的方法,這就給我們保存圖標到數據庫提供了可能,看下是怎麼使用的。在MyApp.java中。
好了,代碼擼到這裏大概就講完了雛形的搭建,但是現在還只是個應用,如何真正使得這個應用變成桌面呢,需要在AndroidManifest.xml中進行一個簡單的設置,
<activity android:name=".MainActivity" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
這樣纔是一個真正的桌面應用了,下一章我們學習下,拖動圖標以及建立桌面菜單分類,可能會改下界面結構的。
差點忘了,這個是目前寫到這裏使用的依賴文件,至於源碼啥的,等這個系列結束會一併上傳,敬請期待。謝謝大家捧場!
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.jakewharton:butterknife:8.5.1' compile 'com.jakewharton:butterknife-compiler:8.5.1' compile 'com.android.support:appcompat-v7:26.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'org.litepal.android:core:1.6.0' testCompile 'junit:junit:4.12' compile 'com.android.support:recyclerview-v7:26.+' }