码迷,mamicode.com
首页 > 移动开发 > 详细

AppWidget实现自定义view的另类实现

时间:2016-08-25 21:36:28      阅读:757      评论:0      收藏:0      [点我收藏+]

标签:

一、鸡汤

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,有了上面的大体介绍,下面我们就这两种情况具体来介绍一下。


只含有textview、button等情况

首先我们要准备一个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>
简要的介绍下属性值:
initialLayout:指定把appwidget放置在桌面上的时候的初始布局。

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>

这里的<meta-data>是必须要写的,名字是固定的,resource就是我们创建的上面的那个xml文件的位置。其中
 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action>

这个是必须要写的一个action,用来接收appwidget更新的广播,后两个为我自己写的广播。下面我们来看看AppwidgetProvider中的代码:

public class WidgetProviderClass extends AppWidgetProvider {
 
}

其实我们只要继承它就可以了,里面的代码我们可以都不写,这样运行就会正常的使用appwidget了。前提是我们的widget的视图必须是没有包含子视图的布局,比如只有一个textview,去你的appwidget里面选择我们的小组件放在桌面,这里我贴一下运行的效果:

技术分享


也许你的效果和我的不一样,那取决于你定义的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的情况


我们修改自己的布局文件,添加一个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;
        }
    }
}

这里我使用了一个类来加载网上的数据,当然你可以用自己的方式去现实数据,这里可能有个方法大家不是很懂,就是那个tread.join()方法,它的作用是在线程执行完run方法之后再执行join后面的代码,我这里使用的目的是做有个同步,也就是在数据下载完成后再执行后面的代码。工具类这里就不贴代码了。

到这里我们就可以运行看下效果了。我的运行效果如下:

技术分享

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

AppWidget实现自定义view的另类实现

标签:

原文地址:http://blog.csdn.net/qq379454816/article/details/52161495

(2)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!