发新帖

【原创】Android Widget详解(二)

[复制链接]
4173 1
前言
上一篇博客我们实现了自定义一个Widget的一些操作,但是没有再Widget中实现任何的点击操作和复杂布局,例如Listview的使用等等.本篇博客就来进行实现一些复杂布局
点击事件的处理
Widget是运行在桌面线程中的,所以我们不能用传统的方式来对Widget进行点击操作,需要用到pendingintent来传递点击事件广播,然后在广播中处理点击事件,对其做出响应.并且我们如果操控Widget中的布局,只能操控Remoteview来达到目的,Remoteview中规定了一些我们可以使用的方法,不提供的方法我们无法实现,只能通过存在的方法变相实现目的.
首先拿到RemoteView
RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.widget_cw);
实现点击之后打开系统时钟和日历(简单,发送Intent即可)
//设置打开系统日历view.setOnClickPendingIntent(R.id.calendarlinear, PendingIntent.getActivity(context, 2, new Intent().setComponent(new ComponentName("com.android.calendar", "com.android.calendar.LaunchActivity")), PendingIntent.FLAG_UPDATE_CURRENT));//设置打开系统时钟view.setOnClickPendingIntent(R.id.clock_layout, PendingIntent.getActivity(context, 2, new Intent().setComponent(new ComponentName("com.android.deskclock", "com.android.deskclock.DeskClock")), PendingIntent.FLAG_UPDATE_CURRENT));
第一个参数是需要设置的控件ID,第二个参数是一个PendingIntent,在Widget中设置监听无法直接用Intent传递,需要用PendingIntent来进行传递。
其格式大概为 set + (控件名) + 控件属性 格式不绝对,仅供参考,所以view中没有提供的方法,就无法进行修改。
实现上述操作十分的简单,我们只需要给view中的哪个"按钮"(这个按钮可以是任何控件)来绑定pendingintent就可以了.
下面实现一个点击在Widget中做出响应
//给两个按钮设置点击监听view.setOnClickPendingIntent(R.id.btn_next, getClickIntent(context, appWidgetIds[0], R.id.btn_next, 0, "com.longlong.Widget.Button.Update"));view.setOnClickPendingIntent(R.id.btn_previous, getClickIntent(context, appWidgetIds[0], R.id.btn_previous, 1, "com.longlong.Widget.Button.Update"));
PendingIntent
使用这样一个方法来获取我们需要的一个基本的PendingIntent(这个方法我自己实现的,非官方)
//给按钮返回PendingIntentprivate PendingIntent getClickIntent(Context context, int widgetId, int viewId, int requestCode, String action) {        //拿到管理对象        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);        //pendingintent中需要的intent,绑定这个类和当前context        Intent i = new Intent(context, MyWidget.class);        //设置action        i.setAction(action); //设置更新动作        //设置bundle        Bundle bundle = new Bundle();        //将widgetId放进bundle        bundle.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);        //放进需要设置的viewId        bundle.putInt("Button", viewId);        i.putExtras(bundle);        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, i, PendingIntent.FLAG_UPDATE_CURRENT);        return pendingIntent;    }
使用这样一个方法来获取我们需要的一个基本的PendingIntent(这个方法我自己实现的,非官方)
设置权限
这段代码很好理解,前文提到过,这里的点击是通过广播机制实现的,所以我们仍然需要再我们的Widget中设置权限,修改Mainifests
        <receiver android:name=".MyWidget">            <intent-filter>                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />                <action android:name="com.longlong.Widget.Button.Update" />                <action android:name="com.longlong.COLLECTION_VIEW_ACTION" />            </intent-filter>            <meta-data                android:name="android.appwidget.provider"                android:resource="@xml/my_widget" />        </receiver>
其中包括两个实现自定义按钮的点击权限还有下午你要实现的listview更新的点击权限(原理相同) 然后我们只需要重写我们的上下翻页实现,也就是重写onReceive方法
//这里用一个switch实现了switch (choice) {            case "com.longlong.Widget.Button.Update":                //因为遥操作view所以拿到remotevie用来操作                RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.widget_cw);                //bundle中会由系统存好下面两个属性,下面是提取出来                Bundle bundle = intent.getExtras();                int widgetId = -1;                int viewId = -1;                try {                    widgetId = bundle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);                    Log.d("widgetId", String.valueOf(widgetId));                    viewId = bundle.getInt("Button");                    Log.d("viewId", String.valueOf(viewId));                } catch (NullPointerException e) {                    return;                }                //根据获得到的viewid进行相应view的操作(这里我是用了一个flipper)                switch (viewId) {                    case R.id.btn_next:                        view.showNext(R.id.flipper);                        break;                    case R.id.btn_previous:                        view.showPrevious(R.id.flipper);                        break;                    default:                        return;                }                AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);                appWidgetManager.updateAppWidget(widgetId, view);                break;
注释标注的很详细,我就不再多写了
在Widget中实现复杂布局
Widget这种神奇的东西要想实现ListView还是需要一番周折的.下面说明以下可以在Widget中实现的一些布局
官方有话这样说:
A RemoteViews object (and, consequently, an App Widget) can support the following layout classes:
  • FrameLayout
  • LinearLayout
  • RelativeLayout
And the following widget classes:
  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper
Descendants of these classes are not supported.不支持这些类的后代
也就是说,只有这些控件能在布局中使用,其他的控件无法再布局中使用,包括这些类的子类,也就是说Widget中的布局是有局限的,不能像再Activity那么为所欲为0.0 而且由于widget运行在桌面进程中,而不是程序主进程,所以对UI的一切操作需要用到RemoteView类来进行操作。
RemoteViewsService
这个是Service的子类,多用于对“GridView,ListView”等控件进行管理。
这么进行设置
  • 设置一个pendingintent模板
view.setPendingIntentTemplate(R.id.data_list_view, getClickIntent(context, appWidgetIds[0], R.id.data_list_view, 3, "com.longlong.COLLECTION_VIEW_ACTION"));
根据后面这个action来在onReceiver对其事件做出响应
  • 设置Adapter
view.setRemoteAdapter(R.id.data_list_view, new Intent(context, MyDataListService.class));
在RemoteViewsService中可以实现控制
//首先我们需要继承RemoteviewServicepublic class MyDataListService extends RemoteViewsService {    @Override    public RemoteViewsFactory onGetViewFactory(Intent intent) {        //返回下文实现的类似Adapter的东东        return new ViewRemoteService(this, intent);    }    //实现一个ViewRemoteService再其中进行adapter的一些操作(官方规定= =)    private class ViewRemoteService implements RemoteViewsService.RemoteViewsFactory {        private Context mContext;        private Intent mIntent;        private ArrayList<String> data = new ArrayList<String>();        public ViewRemoteService(Context context, Intent intent) {            Log.d("构造函数", "执行");            mContext = context;            mIntent = intent;        }        @Override        public void onCreate() {            Log.d("onCreate", "执行");            for (int i = 0; i < 5; i++) {                data.add("这是新闻" + i);            }        }        @Override        public void onDataSetChanged() {        }        @Override        public void onDestroy() {            data.clear();        }        @Override        public int getCount() {            return data.size();        }        @Override        public RemoteViews getViewAt(int position) {        //这里就类似getView函数了,但是实现点击同样是需要remoteview的            RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.item_layout);            views.setTextViewText(R.id.text, data.get(position));            //设置点击监听,反应事件在Widget中的onReceived中实现            views.setOnClickFillInIntent(R.id.item_layout, new Intent().putExtra("POSITION", position));            return views;        }        @Override        public RemoteViews getLoadingView() {            return null;        }        @Override        public int getViewTypeCount() {            return 1;        }        @Override        public long getItemId(int position) {            return position;        }        @Override        public boolean hasStableIds() {            return true;        }    }}
我们回到Widget类的onReceived函数
case "com.longlong.COLLECTION_VIEW_ACTION":     Toast.makeText(context, String.valueOf(intent.getIntExtra("POSITION", -1)), Toast.LENGTH_SHORT).show();
这里实现的是点击之后弹出Toast,响应点击的是哪个

举报 使用道具

回复

精彩评论1

花墨    发表于 2015-8-19 15:03:50 | 显示全部楼层
请证明你是Small Soho博主,或者用微博发条有关逆向未来的消息,另外麻烦整理下排版

举报 使用道具

回复 支持 反对
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表