标签:
appwidget是android中小组件,我们经常说的widget其实是指的那些button、textview、imageview等这些小控件,而appwidget则是嵌入到别的app中的activity中显示的一种视图。通常我们的appwidget都是嵌入到luncher应用中的(我们经常说的桌面其实也是一款app也就是home luncher应用,手机里的应用会在其activity内显示一个启动图标),运行在luncher应用中,而其事件处理都是在本app内的进程中完成的,所以这里就会涉及到跨进程通信,而如果本应用想要跟appwidget的视图所运行的app通信,因为appwidget运行在别的进程中,只能使用remoteview去更新视图,remoteview相比于view具有跨进程的能力但是其支持的视图也是非常有线的,常用的大概就是textview、imageview、imagebutton、button、listview、gridview,其余的像自定义view、recycleview等等都是不支持的,所以appwidget的功能还是非常有限的。如果我们只是使用像textview这种没有子布局的控件那么使用方式是非常简单的,这里会涉及到:AppWidgetProvider、AppWidgetProviderInfo这两个类。而如果涉及到像listview、gridview这种,还会涉及到RemoteViewsService和RemoteViewsFactory。
AppWidgetProvider:该类继承自broadcastreceiver,需要在清单文件中注册<receiver>标签。
AppWidgetProviderInfo:该类只需写xml文件就可以了,xml放在res/xml下面,跟标签为<appwidget-provider>.
RemoteViewsService:该类继承自service,需要在清单文件中注册。
RemoteViewsFactory:该类为RemoteViewsService的内部类,处理service中的工作。
ok,有了上面的大体介绍,下面我们就这两种情况具体来介绍一下。
首先我们要准备一个appwidget用来显示的布局文件,这里我们假定只有一个button,放在layout下面就ok,取名为widget_layout,具体代码就补贴出来了,你自己决定写什么样的效果,不过只能含有我上面说的哪几种情况哦。接下来我们需要准备一个AppWidgetProviderInfo类,上面知道这个类是在res/xml下面新建一个xml文件来自动生成的,具体代码如下类似:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget_layout" android:minHeight="300dp" android:minResizeHeight="90dp" android:minResizeWidth="190dp" android:resizeMode="horizontal|vertical" android:minWidth="200dp"> </appwidget-provider>简要的介绍下属性值:
minHeight、minResizeHeight:前一个是指定appwidget的最小高度,后一个指定在允许重新测量appwidget高度的情况下的最少高度,后一个值必须要小于前一个值,否则其会被忽略。
resizeMode:允许在哪些方向上重新测量,我们都知道appwidget在桌面显示的时候会有横向和纵向上有多少个格子,比如2*2、4*4,系统就是根据其能提供的类型和minHeight的值算出一个新的ResizeHeight,如果这个值小于miniResizeHeight就会自动在对应方向上增加一个格子的大小。
其还有下面的属性:
android:configure:指定appwidget第一次放置在桌面上面的时候需要打开的activity,如果配置了这个属性下面将讲到的AppwidgetProvider中的onUpdate就不会在第一次被调用,以后的添加才执行改调用。
android:previewImage:指定appwidget在预览界面显示的图片,也就是我们选择appwidget的时候显示的界面,如果不设置改属性系统就会给app的icon图标作为预览。
android:widgetCategory="home_screen|searchbox|keyguard":用来设置appwidget可以在那些情况下显示,默认只能在home——screen上面显示。keyguard用来在锁屏的时候显示,searchbox实在搜索页面添加。
android:updatePeriodMillis:自动更新的时间,最少为半个小时。
ok,配置好上面的类之后我们来看看AppWidgetProvieder这个类,前面说了该类是继承自BroadcastReceiver,所以我们要在清单文件中配置<receiver>标签,如下所示:
<receiver android:name=".WidgetProviderClass"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action> <action android:name="com.xinxue.action.TYPE_BTN"></action> <action android:name="com.xinxue.action.TYPE_LIST"></action> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_provider"></meta-data> </receiver>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action>
这个是必须要写的一个action,用来接收appwidget更新的广播,后两个为我自己写的广播。下面我们来看看AppwidgetProvider中的代码:
public class WidgetProviderClass extends AppWidgetProvider { }
也许你的效果和我的不一样,那取决于你定义的appwidget的布局文件的效果。ok,简单的效果是有了,如果你想要给按钮添加上点击事件,我们只需要在appwidgetprovider的类中重写onUpdate方法,如下类似:
public class WidgetProviderClass extends AppWidgetProvider { public static final String BTNACTION = "com.xinxue.action.TYPE_BTN"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); //创建一个广播,点击按钮发送该广播 Intent intent = new Intent(BTNACTION); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.widget_btn, pendingIntent); //如果你添加了多个实例的情况下需要下面的处理 for (int i = 0; i < appWidgetIds.length; i++) { appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews); } } @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case BTNACTION: Toast.makeText(context, "点到我啦!", Toast.LENGTH_SHORT).show(); break; } super.onReceive(context, intent); } }运行之后点击你的按钮就可以看到效果啦。我简单的给你介绍下上面的代码,在onUpdate方法中首先创建一个remoteview实例,再创建一个带有指定action的Intent,然后通过这个intent创建一个能发送广播的pendingIntent,然后调用remoteview的setOnClickPendingIntent方法绑定一个点击事件,点击按钮的时候会发送带有前面action的广播,要主要的是该方法的第一个参数为要点击的view的id。因为前面我们在清单文件中已经注册过这种类型的广播,在这里就可以收到该广播了,我们只需要重写其onReceiver方法,在里面过滤出我们需要的广播,在这里你就可以实现自己的点击逻辑了,比如打开app的首页,这里我简单的图个斯意思意思一下。效果图如下:
我们修改自己的布局文件,添加一个listview,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/kk" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/widget_listview" android:background="#000" android:layout_width="match_parent" android:layout_height="match_parent"> </ListView> </LinearLayout>
OK,前面我们说到,要使用这种带有子布局的情况需要使用remoteviewsService和RemoteViewsFactory,那么我们新建一个类叫RemoteviewsServiceImp,让它继承与RemoteviewsService,然后实现里面的方法,不要忘记在manifest文件里面添加<service>标签,标签内的内容如下:
<service android:name=".RemoteViewServiceImp" android:permission="android.permission.BIND_REMOTEVIEWS"></service>
因为remoteviewsservice的任务都是交给factory去完成的,这里我们就建立一个内部类让它实现remoteviewsfactory接口,然后重写里面的方法,完成后的代码如下:
package com.example.leixinxue.widgettest; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.RemoteViewsService; import java.util.ArrayList; /** * Created by leixinxue on 16-8-8. */ public class RemoteViewServiceImp extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new RemoteviewFactoryImp(); } class RemoteviewFactoryImp implements RemoteViewsFactory { @Override public void onCreate() { } @Override public void onDataSetChanged() { } @Override public void onDestroy() { } @Override public int getCount() { return 0; } @Override public RemoteViews getViewAt(int position) { return null; } @Override public RemoteViews getLoadingView() { return null; } @Override public int getViewTypeCount() { return 0; } @Override public long getItemId(int position) { return 0; } @Override public boolean hasStableIds() { return false; } } }
OK,下面我们就来写里面具体的代码。首先是让listview传到remoteviewsservice这里来,然后通过它传给remoteviewsfactory,在factory里面就是填充listview的布局内容,我们现在建立一个listview的item的布局文件,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/item_textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginBottom="5dp" android:layout_marginLeft="10dp" android:layout_marginTop="5dp" android:text="TextView" android:textColor="#fff" android:textSize="18sp" /> </LinearLayout>
下面我们来到APPwidgetprovider里面的onupdate方法里面修改代码,修改后的代码如下:
@Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); //绑定service用来填充listview中的视图 Intent intent = new Intent(context, RemoteViewServiceImp.class); remoteViews.setRemoteAdapter(R.id.widget_listview, intent); //如果你添加了多个实例的情况下需要下面的处理 for (int i = 0; i < appWidgetIds.length; i++) { appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews); } }
我们来到factory里面绑定listview的视图,完成后的代码如下:
package com.example.leixinxue.widgettest; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.RemoteViewsService; import java.util.ArrayList; /** * Created by leixinxue on 16-8-8. */ public class RemoteViewServiceImp extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new RemoteViewsFactoryImp(this, intent); } private static ArrayList<String> data; public static void loadData() { Thread thread = new Thread(new Runnable() { @Override public void run() { data = HttpUtils.getData(); } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } class RemoteViewsFactoryImp implements RemoteViewsFactory { private Intent requestIntent; private Context requestContext; public RemoteViewsFactoryImp(Context context, Intent intent) { requestContext = context; requestIntent = intent; } @Override public void onCreate() { loadData(); } @Override public void onDataSetChanged() { } @Override public void onDestroy() { } @Override public int getCount() { return data.size(); } @Override public RemoteViews getViewAt(int position) { RemoteViews remoteViews = new RemoteViews(requestContext.getPackageName(), R.layout.widget_item_layout); remoteViews.setTextViewText(R.id.item_textView, data.get(position)); return remoteViews; } @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 false; } } }
到这里我们就可以运行看下效果了。我的运行效果如下:
OK,下面我们给listview加上交互,给每一个item添加上点击事件,需要做需改provider和factory里面的代码,修改完成后的代码如下:
provider中的代码:
package com.example.leixinxue.widgettest; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.Toast; /** * Created by leixinxue on 16-8-8. */ public class WidgetProviderClass extends AppWidgetProvider { public static final String BTNACTION = "com.xinxue.action.TYPE_BTN"; public static final String ITEMCLICK = "com.xinxue.action.TYPE_LIST"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); //绑定service用来填充listview中的视图 Intent intent = new Intent(context, RemoteViewServiceImp.class); remoteViews.setRemoteAdapter(R.id.widget_listview, intent); //添加item的点击事件 Intent intent1 = new Intent(ITEMCLICK); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent1, PendingIntent.FLAG_CANCEL_CURRENT); remoteViews.setPendingIntentTemplate(R.id.widget_listview, pendingIntent); //如果你添加了多个实例的情况下需要下面的处理 for (int i = 0; i < appWidgetIds.length; i++) { appWidgetManager.updateAppWidget(appWidgetIds[i], remoteViews); } } @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); if (intent.getAction().equals(ITEMCLICK)) { Toast.makeText(context, intent.getIntExtra("position", 0) + "", Toast.LENGTH_SHORT).show(); } } }factory中的代码:
package com.example.leixinxue.widgettest; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.RemoteViewsService; import java.util.ArrayList; /** * Created by leixinxue on 16-8-8. */ public class RemoteViewServiceImp extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new RemoteViewsFactoryImp(this, intent); } private static ArrayList<String> data; public static void loadData() { Thread thread = new Thread(new Runnable() { @Override public void run() { data = HttpUtils.getData(); } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } class RemoteViewsFactoryImp implements RemoteViewsFactory { private Intent requestIntent; private Context requestContext; public RemoteViewsFactoryImp(Context context, Intent intent) { requestContext = context; requestIntent = intent; } @Override public void onCreate() { loadData(); } @Override public void onDataSetChanged() { } @Override public void onDestroy() { } @Override public int getCount() { return data.size(); } @Override public RemoteViews getViewAt(int position) { RemoteViews remoteViews = new RemoteViews(requestContext.getPackageName(), R.layout.widget_item_layout); //listview的点击事件 Intent intent = new Intent(WidgetProviderClass.ITEMCLICK); intent.putExtra("position", position); remoteViews.setOnClickFillInIntent(R.id.item_textView, intent); remoteViews.setTextViewText(R.id.item_textView, data.get(position)); return remoteViews; } @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 false; } } }
我们运行看下效果,OK,完美。到这里你可能已经学完了APPwidget的教程了,看Google的官方API你可能学到的也就只有这些。
全球首创让你的APPwidget实现自定义view
博客地址:blog.csdn.net/qq379454816------>欢乐斗佛的博客
我们都知道,产品经理大部分是SB,他们不知道怎么去编程,不知道一个模块的功能怎么去实现,可是他们为了酷炫,就是设计出一些很难实现的效果图,如果它设计一个很难得效果图,而这个效果图无法使用基本的视图去实现,必须要用自定义的方式去实现,而APPwidget又不支持自定义view,难道我们用修改底层framework的方式去实现?这当然是一种解决问题的办法,但是这个通常是无法实现的,于是乎你就百度一下,高级一点的屌丝程序员可能Google一下,结果很让人懵逼啊,能搜到几个条目告诉你怎么用自定义view去实现,可是打开一看~~~~~MD,修改framework,额,再次懵逼~~~。绝境之中遇贵人这种桥段是电视惯用的伎俩,但也不是现实生活中没有这种情况,下面我就教你一种全宇宙独创的方式去实现一种自定义view来实现APPwidget无法实现自定义view的窘境,这种方式简单的一B啊,可是如果我不告诉你,你却一辈子也无法去实现,而一旦思路打开,你的奇思妙想就会如尿崩一发而不可收拾,那就是自定义图片!!!什么?这~~~~~。
我们知道APPwidget是可以使用imageview的,而remoteview中有一个方法可以实现替换imageview中的图片:remoteViews.setImageViewBitmap(int viewid,Bitmap bitmap);两个参数,第一个为我们的imageview的id,第二个就是一个图片,imageview我们不可以动手脚,可是这个bitmap的来源我们就可以自己去把控了,你可以使用一个图片利用bitmapfactory来转换,可以使用xml文件来定义一个图片,最大自由度的使用方式是自定义一个bitmap,然后在这个bitmap上面实现我们的复杂效果。考虑到这篇文章前前后后的写了快一个月了,中间还有好多效果图没有给出,而工作忙的我实在没有大片的时间去完善这篇文章,不过中间的步骤什么的我是描述清楚了,上面两种方式的实现百度上面也有相应的文章,这里就部打算去完善了,重点给这个自定义的东西写一下,大家如果对前面的东西还有不明白的地方可以给我留言或者私信。
ok,废话说了一大篇,该是进入正题了。要实现一个自定义的bitmap,我们首先想到是继承 bitmap(有想到继承view的可以在下面排个队,到我这里领赏),可是当继承它的时候你会发现报错了,鼠标一上去一看~~额,bitmap是final类型的,你妹哦!android中图片还有一种方式就是drawable,该方法可以正常使用,不过其尺寸什么的不好去控制,转换bitmap的时候也好麻烦,这里我们就不用该方法了,我们知道自定义都离不开canvas,而构建一个canvas的时候其构造方法中可以传入一个bitmap,那我们何不就此入手实现我们的效果图!这里我说下思路,canvas就好比一个画布,而bitmap就好比一个布,我们给画布上面的布替换成bitmap这块布,然后所有的东西都画到这个布上面,这样bitmap就有内容了,再拿到这个bitmap我们就可以实现我们的目的了。大概就是这样一个过程,我先贴上我在乐视管家项目中的效果图:
原文地址:http://blog.csdn.net/qq379454816/article/details/52161495
标签:
原文地址:http://blog.csdn.net/qq379454816/article/details/52161495