天天看點

Android小部件Widget開發過程中的坑和總結概述AndroidManifest.xmlOptionsUsing App Widgets with Collections

概述

官方參考

Build an App Widget

效果圖

放張效果圖,這是我玩的桌面 app 檔案夾

Android小部件Widget開發過程中的坑和總結概述AndroidManifest.xmlOptionsUsing App Widgets with Collections

先在這裡重點說下坑,再按部就班的碼代碼

清單不顯示内容

看代碼寫的沒問題,看manifest.xml也沒有問題呀,就是不顯示

把别人的代碼複制過來一一比對

打臉呀,權限申請時permission寫成了permision,這問題出的,有點?想撞牆呀!

特别提醒,有關這些共用的東西,如果能複制就别自己敲了吧,就算敲了,也請確定自己敲的是對的,不然哪都沒問題,就是不顯示,那最沒問題的地方往往容易出問題呀

清單項目點選之後無法打開相應的app

在模拟器測試都沒有問題,但一到真機上就不行,着實讓自己郁悶了好久,直接連接配接真機進行debug,代碼沒有任何問題,log日志輸出都正常

于是檢視官方文檔,沒毛病呀,決定放棄了好吧,搞了我那麼多天,還是無果

換部手機測試,小米的,可以點開,隻是問你是否允許,當然允許,然後就都打開了。華為的問題?

找遍設定沒看到相關資訊呀?是我權限問題?無意點開自啟動裡的選項,我蒙,是否允許其他應用打開,還有這一說,全部打開

然後,就,好了,吐血呀!

項目排序

清單項目想要更換,确發現 widget 支援的功能有限,隻能從資料源上下手了,另外寫一支排序的 activity ,借助RecyclerView的ItemTouchHelper來實作拖動

我的另一文章有說明 RecyclerView借助ItemTouchHelper實作拖動排序功能

AndroidManifest.xml

Receiver

切記裡面的字母不要弄錯,最好複制粘貼再修改相對應自定義的地方就好,一個字母的錯誤搞了我一天,吐血

<receiver android:name=".desktop.DesktopWidget">
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/widget_desktop_options" />
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
</receiver>
           

Service

如果小部件中使用到了清單項如 ListView,GridView 等,在綁定資料時需要使用 RemoteViewsService 并提供一個 RemoteViewsFactory 執行個體來填充資料 而非 Adapter

再提,這裡面一定不能敲錯字母,特别是那個 permission 也一定要有,不然無法綁定資料

<service
    android:name=".desktop.DesktopViewsService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />
           

Options

res/xml/

widget_desktop_options.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:initialLayout="@layout/widget_desktop"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>
           

常用參數

Size
  • 尺寸大小最終以單元格資料來顯示,但定義時為 dp
  • 單元格數轉換基本工式 size = 70 x cells - 30
  • 如:1格 = 70 x 1 - 30 = 40dp
  • 最小尺寸定義時最好不要超過 4 個單元格就是 250dp
updatePeriodMillis

更新時間毫秒數,即間隔多少時間呼叫一次 onUpdate() 方法

initialLayout

加載布局檔案

Using App Widgets with Collections

小部件中使用清單項

官方參考

Google Develper

AppWidgetProvider

public class DesktopWidget extends AppWidgetProvider {

    public String ACTION_START_ACTIVITY = "STARTACTIVITYACTION";

    public DesktopWidget() {
        super();
    }

    //當接收到廣播的時候會被調用
    //onUpdate, onEnabled等時都會發送廣播
    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        pub.log("onReceive:" + intent.getAction());
        if (intent.getAction().equals(ACTION_START_ACTIVITY)) {
            String strPackage = intent.getStringExtra("app_package");
            Intent appIntent = context.getPackageManager().getLaunchIntentForPackage(strPackage);
            pub.log(appIntent.getPackage());
            if (appIntent != null) {
                appIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                pub.log("start activity");
                context.startActivity(appIntent);
                pub.log("finish");
            }
        }
    }

    //被添加或者被更新時調用
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);

        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }
    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        pub.log("onUpdate:" + appWidgetId);
        // Construct the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_desktop);
        //設定小部件上 TextView 值
        //views.setTextViewText(R.id.tv_widget_desktop_title, "ZGTools");
        //setRemoteAdapter設定Listivew,Gridview等複雜布局的adapter
        //第一個參數為要綁定的widget的Gridview的id,第二個參數為RemoteViewsService的intent
        Intent intent = new Intent(context, DesktopViewsService.class);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        views.setRemoteAdapter(R.id.gv_app_group, intent);

        //綁定 item 的定點事件
        //Activity方式測試不成功,改用廣播的方式進行發送并執行事件
        //此處不能是空的Intent 否則廣播無法發送
        //定義Intent事件模闆,相當于公用設定,再在fillIntent裡面設定具體的 extra 值
        Intent templateIntent = new Intent(context, DesktopWidget.class);
        templateIntent.setAction(ACTION_START_ACTIVITY);
        templateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        templateIntent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, templateIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        views.setPendingIntentTemplate(R.id.gv_app_group, pendingIntent);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }


    //被添加或者更改大小時調用,在這個方法中可以選擇性的根據界面大小顯示或隐藏某些控件
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        updateAppWidget(context, appWidgetManager, appWidgetId);
    }
    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
        // Enter relevant functionality for when the first widget is created
    }
    @Override
    public void onDisabled(Context context) {
        super.onDisabled(context);
        // Enter relevant functionality for when the last widget is disabled
    }
}
           

RemoteViewsService

public class DesktopViewsService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new DesktopViewsFactory(this, intent);
    }
}
           

RemoteViewsFactory

類型于 BaseAdapter,重點是 onCreate() 加載資料, getViewAt() 方法傳回布局綁定資料

public class DesktopViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private Context mContext;
    private int mAppWidgetId;
    private List<AppPackage> lstApps = new ArrayList<AppPackage>();

    public DesktopViewsFactory(Context context, Intent intent){
        this.mContext = context;
        this.mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    @Override
    public void onCreate() {
        DesktopDbHelper dbHelper = new DesktopDbHelper(mContext);
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        String strSql = "Select * from tb_app";
        try {
            Cursor cursor = db.rawQuery(strSql, null);
            if (cursor != null && cursor.getCount() > 0) {
            	lstApps.clear();
                while (cursor.moveToNext()) {
                    byte[] blob = cursor.getBlob(cursor.getColumnIndex("app_icon"));
                    Bitmap bmpIcon = BitmapFactory.decodeByteArray(blob, 0, blob.length);
                    String strLabel = cursor.getString(cursor.getColumnIndex("app_label"));
                    String strPackage = cursor.getString(cursor.getColumnIndex("app_package"));
                    lstApps.add(new AppPackage(strLabel, strPackage, bmpIcon));
                }
            }
            if (cursor != null) {
                cursor.close();
            }
        } catch (Exception e) {
            db.close();
            e.printStackTrace();
        }
        db.close();
    }

    @Override
    public void onDataSetChanged() {
    	//這裡我加了重新加載資料的調用,用于更新小部件的清單内容
		onCreate();
	}
    @Override
    public void onDestroy() {}
    @Override
    public int getCount() {return 0;}

    @Override
    public RemoteViews getViewAt(int i) {
        if (i < 0 || i >= lstApps.size()) return null;
        //Construct a remote views item based on the app widget item XML file
        RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget_desktop_item);
        views.setTextViewText(R.id.tv_widget_desktop_label, lstApps.get(i).getAppLabel());
        views.setImageViewBitmap(R.id.iv_widget_desktop_icon, lstApps.get(i).getAppIcon());

        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        bundle.putString("app_package", lstApps.get(i).getAppPackage());
        intent.putExtras(bundle);
        views.setOnClickFillInIntent(R.id.iv_widget_desktop_icon, intent);
        views.setOnClickFillInIntent(R.id.tv_widget_desktop_label, intent);

        return views;
    }

    @Override
    public RemoteViews getLoadingView() { return null;}
    @Override
    public int getViewTypeCount() {return 0;}
    @Override
    public long getItemId(int i) { return 0;}
    @Override
    public boolean hasStableIds() { return false; }
}
           

DesktopActivity

//更新widget布局
    private void updateWidget(){
        AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
        ComponentName componentName = new ComponentName(this, DesktopWidget.class);
        int[] ids = widgetManager.getAppWidgetIds(componentName);
        widgetManager.notifyAppWidgetViewDataChanged(ids, R.id.gv_app_group);
    }