Android開發自己的桌面應用,Luancher原來也是如此簡單(上)

      最近研究了下開發桌面應用,在這裏跟大家分享下,廢話不多說,先來看下基本的效果圖。



應用列表



點擊圖標,打開應用


又是我的華爲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();
        //第三步,調用compressBitmap對象壓縮爲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.+'
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章