精华内容
下载资源
问答
  • 博客《桌面widget详解(三)——桌面widget中的控件交互方法》对应源码,博客地址:http://blog.csdn.net/harvic880925/article/details/41598819
  • 这篇我们依然撇开播放器,讲一讲桌面widget中最基本的功能,大家先把最基本的给弄懂了,下篇再来实现播放器的桌面widget,可见实现一个复杂的桌面widget是多么的困难。 先看下这篇的实现效果: 在widget中实现一个...

    这篇我们依然撇开播放器,讲一讲桌面widget中最基本的功能,大家先把最基本的给弄懂了,下篇再来实现播放器的桌面widget,可见实现一个复杂的桌面widget是多么的困难。

    先看下这篇的实现效果:

    在widget中实现一个text和两个button,当点击第一个button的时候,text中显示一个随机数。

    在上一篇中《桌面widget详解(一)——基本demo构建》,我们简单介绍了怎么显示了widget,但如何让widget中的按钮得到交互等问题还没有涉及,这篇中,虽然是新开的布局,但有关显示的部分,我就不再细讲,大家可以参考上一篇。

    一、widget布局

    本来我不打算列出这部分内容,但考虑到这个布局是新的,所以还是给大家简单列一下吧。但有关xml下的<appwidget-provider和AndroidManifest.xml下的注册就不再讲了。不过,为了区分,我把丑小孩的头像改成了小猫咪。其它没变化,看代码就知道了,下面列出widget的布局:

    <?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:background="#33000000"
        android:orientation="vertical" >
     
        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dip"
            android:layout_marginTop="30dip"
            android:gravity="center_horizontal"
            android:text="text"
            android:textSize="14sp" />
     
        <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="btn_1" />
     
        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dip"
            android:text="btn2" />
     
    </LinearLayout>
    

    这个布局很简单,一个textView和两个btn,没什么难度。

     

    二、AppWidget中控件交互(ExampleAppWidgetProvider.java)

    1、发送广播与按钮事件绑定

    因为appwidget运行的进程和我们创建的应用不在一个进程中,所以我们也就不能像平常引用控件那样来获得控件的实例。这时候,我们就要靠RemoteViews,直译成中文应该是远程视图; 也就是说通过这个东西我们能够获得不在同一进程中的对象,这也就为我们编写appwidget的处理事件提供了帮助。我们使用一下代码来创建一个RemoteViews。

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
     
    // 绑定事件
    remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
    

    第一句创建了一个remoteViews实例,然后将其中的按钮与点击事件绑定,后面的参数中又出来了一个PendingIntent。下面看看PendingIntent又是个什么玩意。

    intent英文意思是意图,pending表示即将发生或来临的事情。
    PendingIntent这个类用于处理即将发生的事情。比如在通知Notification中用于跳转页面,但不是马上跳转。所以我们可以将它理解成一个封装成消息的intent的。即这个intent并不是立即start,而是像消息一样被发送出去,等接收方接到以后,再分析里面的内容。
     

    Intent intent = new Intent();
    intent.setClass(context, ExampleAppWidgetProvider.class);
    intent.setData(Uri.parse("harvic:" + R.id.btn1));
     
    // 设置pendingIntent的作用
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
    

    可以看到,PendingIntent是Intent的封装,在构造PendingIntent前,也要先构造一个Intent,并可以利用Intent的属性传进去action,Extra等,同样,在接收时,对方依然是接收Intent的,而不是接收PaddingIntent。这个问题,我们后面可以看到。
    PendingIntent.getBroadcast(context, 0,intent, 0);指从系统中获取一个用于可以发送BroadcastReceiver广播的PendingIntent对象。

     

    讲完这两个之后,我们看一下OnUpdate的内容:

    其中:

    String broadCastString = "harvic.provider";
    
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    		int[] appWidgetIds) {
     
    	Intent intent = new Intent();
    	intent.setClass(context, ExampleAppWidgetProvider.class);
    	intent.setData(Uri.parse("harvic:" + R.id.btn1));
    	
    	// 设置pendingIntent的作用
    	PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
    	RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    	remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
    	// 更新Appwidget
    	appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    }
    

    这里的总体步骤就是,构造一个RemoteView,然后利用updateAppWidget()将构造的RemoteView更新指定的widget界面。

    先看构造pendingIntent的过程:

    Intent intent = new Intent();
    intent.setClass(context, ExampleAppWidgetProvider.class);
    intent.setData(Uri.parse("harvic:" + R.id.btn1));
     
    // 设置pendingIntent的作用
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
    

    首先是构造一个广播时发送的intent,注意这个intent构造的是显示intent,直接将这个广播发送给ExampleAppWidgetProvider,附带的数据通过Data传送(最后附录中会讲为什么不通过putExtra传送额外值),这里传送过去的btn1的id值。

     

    然后通过PendingIntent.getBroadcast();将intent封装到pendingIntent中,以待发送。
    在构造了pendingIntent之后,就是将这个pendingIntent与btn1绑定,当用户点击btn1的时候,将广播发送出去。代码如下:

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
    

    最后,是将这个remoteView更新到所有widget上。(因为用户对某一个apps可以创建多个widget,要保持所有的widget状态统一)

    appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    

    注意这里有个appWidgetIds,这个参数是通过OnUpdate()传过来的,它是一个int数组,里面存储了用户所创建的所有widget的ID值。更新的时候也是通过widget的ID值,一个个更新的。
    再絮叨一遍,我们这里做了两件事:

    (1)、将按钮控件(R.id.btn1)与pendingIntent绑定。当用户点击按钮时,就会把所构造的intent发送出去。

    涉及代码为:

    Intent intent = new Intent();
    intent.setClass(context, ExampleAppWidgetProvider.class);
    intent.setData(Uri.parse("harvic:" + R.id.btn1));
     
    // 设置pendingIntent的作用
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
    

    (2)、更新所有的widget

    主要利用updateAppWidget(appWidgetIds, remoteViews);将remoteView根据widget的id值,一个个更新界面,涉及代码为:

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    // 更新Appwidget
    appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    

    可以看到,这里如果不算绑定按钮的话,其实什么都没有更新,因为RemoteView除了绑定控件的点击事件,还可以设置textView的文字,ImageVIew的图片Resource等,我们这里都还没有涉及

    2、接收广播

    由于我们在创建广播的Intent时,使用的显示Intent,所以我们的广播不需要注册就会发到这们这个类(ExampleAppWidgetProvider.java)里面。

    在接收到广播后,我们先判断Intent中是不是包含data,因为我们在发送广播时放data中塞了数据(btn1的ID),所以只要data中有数据就可以判定是用户点击btn1发来的广播。然后同样利用RemoteView将textView的文字改成btn click加一串随机数字,代码如下:

    @Override
    public void onReceive(Context context, Intent intent) {
    	
    	if (intent == null) {
    		return;
    	}
     
    	String action = intent.getAction();
    	if (broadCastString.equals(action)) {
    		// 只能通过远程对象来设置appwidget中的控件状态
    		RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
    				R.layout.example_appwidget);
     
    		// 通过远程对象将按钮的文字设置为”一个随机数”
    		Random random1 = new Random();
    		remoteViews.setTextViewText(R.id.text,"btn click:" + random1.nextInt());
     
    		// 获得appwidget管理实例,用于管理appwidget以便进行更新操作
    		AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
     
    		// 相当于获得所有本程序创建的appwidget
    		ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
     
    		// 更新appwidget
    		appWidgetManager.updateAppWidget(componentName, remoteViews);
    	}
    	super.onReceive(context, intent);
    }
    

    同样还是利用updateAppWidget()函数来更新widget,对比OnUpdate()中的代码,这里有两点不同:

    1、少了btn绑定,仅仅是更新界面,代码为:

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
    		R.layout.example_appwidget);
     
    // 通过远程对象将按钮的文字设置为”一个随机数”
    Random random1 = new Random();
    remoteViews.setTextViewText(R.id.text,"btn click:" + random1.nextInt());
    

    在这里,我们将R.id.text的文字更新为btn click加一个随机数。因为每次点击按钮都会发送一个消息,所以每次点击产生的随机数是不同的,在界面上可以明显的表现出来。

     

    2、改变了更新界面的方式

    在OnUpdate中,我们更新界面是通过传过来的widget的id数组来更新所有widget的。而这里是通过获取ComponentName来更新的。其实这里还有另一种实现方式,即我们可以把OnUpdate中传过来的appWidgetIds保存起来,在这里同样使用OnUpdate中的appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);来更新。但我更推荐这里利用ComponentName的这种方式来更新,因为当我们的应用程序进程一被杀掉,当应用程序再起来的时候,使用ComponentName这种更新方式的代码还是可以继续响应的,而利用保存appWidgetIds的代码是不会响应的。
     

    三、进阶——如何响应多个按钮控件

    上面的例子中,我们简单实现了所谓的交互,但并没有办法识别出当前是哪个控件发出的,并根据不同的控件做出响应,先看看这部分效果:

    下面对上面的代码进行补充,首先在发送时,就应该加以区别当前是哪个控件发出的intent,代码如下:

    private PendingIntent getPendingIntent(Context context,int resID){
    	Intent intent = new Intent();
    	intent.setClass(context, ExampleAppWidgetProvider.class);
    	intent.setData(Uri.parse("harvic:" + resID));
    	
    	PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent,0);
    	return pendingIntent;
    }
     
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    		int[] appWidgetIds) {
    	RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    	
    	remoteViews.setOnClickPendingIntent(R.id.btn1, getPendingIntent(context, R.id.btn1));
    	
    	remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
    	// 更新Appwidget
    	appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    }
    

    先看OnUpdate中设置RemoteView的代码:

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
     
    remoteViews.setOnClickPendingIntent(R.id.btn1, getPendingIntent(context, R.id.btn1));
     
    remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
    // 更新Appwidget
    appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
    

    创建一个RemoteView,然后将btn1,btn2分别进行绑定,这里我将PendingIntetn进行了封装:

    private PendingIntent getPendingIntent(Context context,int resID){
    	Intent intent = new Intent();
    	intent.setClass(context, ExampleAppWidgetProvider.class);
    	intent.setData(Uri.parse("harvic:" + resID));
    	
    	PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent,0);
    	return pendingIntent;
    }
    

    要设置data域的时候,把控件ID设置进去,因为我们在绑定的时候,是将同一个ID绑定在一起的,所以哪个控件点击,发送的intent中data中的id就是哪个控件的id,绑定Id的代码就是下面这行:

    remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
    

    然后就是接收的部分:

     

    接收时主要就是先根据传送过来的Intent,找到data中的控件id:

    Uri data = intent.getData();
    int resID = -1;
    if(data != null){
    	resID = Integer.parseInt(data.getSchemeSpecificPart());
    }
    

    然后根据ID,定制RemoteView的TextView的字体内容

    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    Random random1 = new Random();
     
    switch (resID) {
    case R.id.btn1:		
    	remoteViews.setTextViewText(R.id.text,"btn1 click:" + random1.nextInt());
    	break;
    case R.id.btn2:
    	remoteViews.setTextViewText(R.id.text,"btn2 click:" + random1.nextInt());
    	break;
    }
    

    同样,最后是更新所有的wiget界面:

    // 获得appwidget管理实例,用于管理appwidget以便进行更新操作
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    // 相当于获得所有本程序创建的appwidget
    ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
    // 更新appwidget
    appWidgetManager.updateAppWidget(componentName, remoteViews);
    

    经过上面的讲解,总体的接收代码就是这样的:

    @Override
    public void onReceive(Context context, Intent intent) {
    	
    	Uri data = intent.getData();
    	int resID = -1;
    	if(data != null){
    		resID = Integer.parseInt(data.getSchemeSpecificPart());
    	}
    	
    	// 通过远程对象将按钮的文字设置为”一个随机数”
    	RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    	Random random1 = new Random();
     
    	switch (resID) {
    	case R.id.btn1:		
    		remoteViews.setTextViewText(R.id.text,"btn1 click:" + random1.nextInt());
    		break;
    	case R.id.btn2:
    		remoteViews.setTextViewText(R.id.text,"btn2 click:" + random1.nextInt());
    		break;
    	}
     
    	// 获得appwidget管理实例,用于管理appwidget以便进行更新操作
    	AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    	// 相当于获得所有本程序创建的appwidget
    	ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
    	// 更新appwidget
    	appWidgetManager.updateAppWidget(componentName, remoteViews);
     
    	super.onReceive(context, intent);
    }
    

    好了,到这所有的代码都讲完了。

    四、附(通过匿名Intent发送广播------不推荐):

    在很多例子中使用的是匿名Intent来发送广播,即设定intent的Action来发送广播,这种方法我是极不推荐的,因为不能识别发送控件的id,这主要是由于pendingIntent的原因。针对这个工程,我也写了一个匿名Intent广播的例子,在这我就不讲了,大家可以看源码。

    展开全文
  • 最近需要编写一个日期时间的桌面Widget用来关联日历程序,以前很少写桌面Widget。对这方面技术不是很熟悉,今天花时间重新整理了一下,顺便把编写一个简单时间日期程序过程记录下来。 桌面Widget其实就是一个显示...
  • Android的桌面Widget的Demo
  • 桌面widget备忘录

    2013-01-17 17:11:25
    仿三星桌面widget备忘录,支持多个备忘录
  • Android桌面(2):Android桌面widget 原文:https://blog.csdn.net/harvic880925/article/details/41445407 一、概述 AppWidget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用...

    Android桌面二:Android桌面widget

    原文:https://blog.csdn.net/harvic880925/article/details/41445407

    一、概述
         AppWidget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。官方文档地址:《App Widgets》
         这里涉及到两个方面的内容:AppWidgetProvider类和appwidget-provider标签;
    

    1、appwidget-provider标签:

    用来定义桌面widget的大小,初始状态等等信息的,它的位置应该放在res/xml文件夹下,具体的xml参数如下:
                        android:minWidth: 最小宽度
                        android:minHeight: 最小高度
                        android:updatePeriodMillis: 更新widget的时间间隔(ms),"86400000"为1个小时
                        android:previewImage: 预览图片(长按Home键出现的预览图片)
                        android:initialLayout: 加载到桌面时对应的布局文件
                        android:resizeMode: widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
                        android:widgetCategory: widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。
                        android:initialKeyguardLayout: 加载到锁屏界面时对应的布局文件 
    

    2、AppWidgetProvider类:

    当widget要实时更新,要响应用户操作时,就需要额外的类来辅助处理了,这个类就是AppWidgetProvider。
    由于AppWidgetProvider要接收到当前widget的状态(是否被添加,是否被删除等),所以要接收通知,必然是派生自BroadcastReceiver。

           AppWidgetProvider中的广播处理函数如下:(根据不同的使用情况,重写不同的函数)
           
                    onUpdate():
                             在3种情况下会调用OnUpdate()。onUpdate()是在main线程中进行,因此如果处理需要花费时间多于10秒,处理应在service中完成。
                                       (1)在时间间隔到时调用,时间间隔在widget定义的android:updatePeriodMillis中设置;
                                       (2)用户拖拽到主页,widget实例生成。无论有没有设置Configureactivity,我们在Android4.4的测试中,当用户拖拽图片至主页时,widget实例生成,会触发onUpdate(),然后再显示activity(如果有)。这点和资料说的不一样,资料认为如果设置了Configure acitivity,就不会在一开始调用onUpdate(),而实验显示当实例生成(包括创建和重启时恢复),都会先调用onUpate()。在本例,由于此时在preference尚未有相关数据,创建实例时不能有效进行数据设置。
                                       (3)机器重启,实例在主页上显示,会再次调用onUpdate()
    
                    onDeleted(Context,int[]):
                             当 widget 被删除时被触发。
                            
                    onEnabled(Context):
                             当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。
                   
                    onDisabled(Context):
                             当最后1个 widget 的实例被删除时触发。
                            
                    onReceive(Context,Intent):
                             在接收到广播时,调用。
    

    3,清单配置

    <receiver
        android:name=".LedClockWidget"
       android:label="@string/app_name">
        <intent-filter>
            <actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
           android:name="android.appwidget.provider"
            android:resource="@xml/my_lock"/>
    </receiver>
    
    二、实战

    LedClockWidget .java

    com/example/administrator/LedClockWidget.java
    packagecom.example.administrator;
    
    import android.annotation.SuppressLint;
    import android.appwidget.AppWidgetManager;
    import android.appwidget.AppWidgetProvider;
    import android.content.ComponentName;
    import android.content.Context;
    import android.os.Handler;
    import android.os.Message;
    import android.widget.RemoteViews;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class LedClockWidget extends AppWidgetProvider {
        private Timer mTimer = new Timer();
        private AppWidgetManager mAppWidgerManager;
        private Context mContext;
        //将0-9的液晶数字图片定义为数组
        private int[] digits = new int[]{R.drawable.p0, R.drawable.p1, R.drawable.p2, R.drawable.p3, R.drawable.p4, R.drawable.p5, R.drawable.p6, R.drawable.p7, R.drawable.p8, R.drawable.p9,};
        //将显示小时、分钟、秒钟的ImageView定义为数组
        private int[] digitViews = new int[]{R.id.img01, R.id.img02, R.id.img04, R.id.img05, R.id.img07, R.id.img08,};
    
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
            this.mAppWidgerManager =appWidgetManager;
            this.mContext =context;
            //定义计时器
            mTimer = new Timer();
            //启动周期性调度
            mTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    //发送空消息,通知界面更新
                    handler.sendEmptyMessage(0x123);
                }
            }, 0, 1000);
            super.onUpdate(context, appWidgetManager, appWidgetIds);
        }
    
        @SuppressLint("HandlerLeak")
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 0x123) {
                    RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.main);
                    //定义SimpleDateFormat对象
                    SimpleDateFormat df = new SimpleDateFormat("HHmmss");
                    //将当前时间格式化为HHmmss的形式
                    String timeStr = df.format(new Date());
                    for (int i = 0; i <timeStr.length(); i++) {
                        //将第i个数字字符zh转换为对应的数字
                        int num = timeStr.charAt(i) - 48;
                        //将第i个图片设为对应的液晶数字图片
                        views.setImageViewResource(digitViews[i], digits[num]);
                    }
                    //将APPWidgetProvider子类实例包装成ComponentName对象
                    ComponentName componentName = new ComponentName(mContext, LedClockWidget.class);
                    //调用APPWidgetManager将RemoteViews添加到ComponentName中
                    mAppWidgerManager.updateAppWidget(componentName, views);
                }
                super.handleMessage(msg);
            }
        };
    }
    

    layout/main.xml

    <?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="horizontal">
    
        <ImageView
            android:id="@+id/img01"
            android:layout_width="30dp"
            android:layout_height="30dp" />
    
        <ImageView
            android:id="@+id/img02"
            android:layout_width="30dp"
            android:layout_height="30dp" />
    
        <ImageView
            android:layout_width="10dp"
            android:layout_height="30dp"
            android:background="@drawable/maohao" />
    
        <ImageView
            android:id="@+id/img04"
            android:layout_width="30dp"
            android:layout_height="30dp" />
    
        <ImageView
            android:id="@+id/img05"
            android:layout_width="30dp"
            android:layout_height="30dp" />
    
        <ImageView
            android:layout_width="10dp"
            android:layout_height="30dp"
            android:background="@drawable/maohao" />
    
        <ImageView
            android:id="@+id/img07"
            android:layout_width="30dp"
            android:layout_height="30dp" />
    
        <ImageView
            android:id="@+id/img08"
            android:layout_width="30dp"
            android:layout_height="30dp" />
    </LinearLayout>
    

    xml/my_lock.xml

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/main"
        android:minHeight="70dp"
        android:minWidth="150dp"
        android:previewImage="@drawable/bbb"
        android:resizeMode="horizontal|vertical"
        android:updatePeriodMillis="1000" />
    

    app/src/main/AndroidManifest.xml

     <?xml version="1.0" encoding="utf-8"?>
        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            package="com.example.administrator">
        
            <uses-permission android:name="android.permission.INTERNET" />
        
            <application
                android:allowBackup="true"
                android:icon="@mipmap/ic_launcher"
                android:label="@string/app_name"
                android:supportsRtl="true">
                <activity android:name=".MainActivity">
                    <intent-filter>
                        <action android:name="android.intent.action.MAIN" />
        
                        <category android:name="android.intent.category.LAUNCHER" />
                    </intent-filter>
                </activity>
        
                <receiver
                    android:name=".LedClockWidget"
                    android:label="@string/app_name">
                    <intent-filter>
                        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                    </intent-filter>
                    <meta-data
                        android:name="android.appwidget.provider"
                        android:resource="@xml/my_lock" />
                </receiver>
        
        
            </application>
        
        </manifest>
    
    三、可能出现的错误:

    1、有关布局错误

    在构造Widget布局时,AppWidget支持的布局和控件非常有限,有如下几个:

    AppWidget支持的布局:

    • FrameLayout
    • LinearLayout
    • RelativeLayout
    • GridLayout

    AppWidget支持的控件:

    • AnalogClock

    • AnalogClock

    • Button

    • Chronometer

    • ImageButton

    • ImageView

    • ProgressBar

    • TextView

    • ViewFlipper

    • ListView

    • GridView

    • StackView

    • AdapterViewFlipper

    除此之外的所有控件(包括自定义控件)都无法显示,无法显示时,添加出来的widget会显示“加载布局出错”

    2、appwidget-provider出现错误
    如果appwidget-provider页面出现错误提示:error:No resource identifier found for attribute ‘widgetCategory’ in package’android’
    这是由于buildtarget应该在17以上

    三:Android4.0新增的显示数据集的桌面控件

    setRemoteAdapter(intviewId,Intentintent):该方法可以使用Intent更新RemoteViews中viewId对应的组件。
    上面方法的Intent参数应该封装一个RemoteViewsService参数,RemoteViewsService虽然继承了Service组件,但它的主要作用是为RemoteViews中viewId对应的组件提供列表项。
    由于Intent参数负责提供列表项,因此viewId参数对应的组件可以是ListView、GridView、StackView和AdapterViewFlipper等,这些组件都是AdapterView的子类,由此可见RemoteViewsService负责提供的对象,应该是一个类似于Adapter的对象。

    RemoteViewsService通常用于被继承,继承该基类时需要重写它的onGetViewFactory()方法,该方法就需要返回一个类似于Adapterr对象——但不是Adapter,而是RemoteViewsFactory对象,RemoteViewsFactory的功能完全类似于Adapter。

    实例:

    StackWidgetService.java

    		package org.crazyit.desktop;
    
    		import android.content.Context;
    		import android.content.Intent;
    		import android.widget.RemoteViews;
    		import android.widget.RemoteViewsService;
    
    		public class StackWidgetService extends RemoteViewsService
    		{
    			// 重写该方法,该方法返回一个RemoteViewsFactory对象。
    			// RemoteViewsFactory对象的的作用类似于Adapter,
    			// 它负责为RemoteView中指定组件提供多个列表项。
    			@Override
    			public RemoteViewsFactory onGetViewFactory(Intent intent)
    			{
    				return new StackRemoteViewsFactory(this.getApplicationContext(),
    					intent);  //①
    			}
    			class StackRemoteViewsFactory implements
    			RemoteViewsService.RemoteViewsFactory
    			{
    				// 定义一个数组来保存该组件生成的多个列表项
    				private int[] items = null;
    				private Context mContext;
    				public StackRemoteViewsFactory(Context context, Intent intent)
    				{
    					mContext = context;
    				}
    				@Override
    				public void onCreate()
    				{
    					// 初始化items数组
    					items = new int[] { R.drawable.bomb5, R.drawable.bomb6,
    						R.drawable.bomb7, R.drawable.bomb8, R.drawable.bomb9,
    						R.drawable.bomb10, R.drawable.bomb11, R.drawable.bomb12,
    						R.drawable.bomb13, R.drawable.bomb14, R.drawable.bomb15,
    						R.drawable.bomb16
    					};
    				}
    				@Override
    				public void onDestroy()
    				{
    					items = null;
    				}
    				// 该方法的返回值控制该对象包含多少个列表项
    				@Override
    				public int getCount()
    				{
    					return items.length;
    				}
    				// 该方法的返回值控制各位置所显示的RemoteViews
    				@Override
    				public RemoteViews getViewAt(int position)
    				{
    					// 创建RemoteViews对象,加载/res/layout目录下widget_item.xml文件
    					RemoteViews rv = new RemoteViews(mContext.getPackageName(),
    						R.layout.widget_item);
    					// 更新widget_item.xml布局文件中的widget_item组件
    					rv.setImageViewResource(R.id.widget_item,
    						items[position]);
    					// 创建Intent、用于传递数据
    					Intent fillInIntent = new Intent();
    					fillInIntent.putExtra(StackWidgetProvider.EXTRA_ITEM, position);
    					// 设置当单击该RemoteViews时传递fillInIntent包含的数据
    					rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
    					// 此处使用让线程暂停0.5秒来模拟加载该组件
    					try
    					{
    						System.out.println("加载【" + position + "】位置的组件");
    						Thread.sleep(500);
    					}
    					catch (InterruptedException e)
    					{
    						e.printStackTrace();
    					}
    					return rv;
    				}
    				@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;
    				}
    				@Override
    				public void onDataSetChanged()
    				{
    				}
    			}    
    		}
    

    widget_item.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/widget_item"
    android:layout_width="120dp"
    android:layout_height="120dp"
    android:gravity="center"/>
    

    StackWidgetProvider.java

    		package org.crazyit.desktop;
    
    		import android.app.PendingIntent;
    		import android.appwidget.AppWidgetManager;
    		import android.appwidget.AppWidgetProvider;
    		import android.content.ComponentName;
    		import android.content.Context;
    		import android.content.Intent;
    		import android.widget.RemoteViews;
    		import android.widget.Toast;
    
    		public class StackWidgetProvider extends AppWidgetProvider
    		{
    			public static final String TOAST_ACTION
    				= "org.crazyit.desktop.TOAST_ACTION";
    			public static final String EXTRA_ITEM 
    				= "org.crazyit.desktop.EXTRA_ITEM";
    
    			@Override
    			public void onUpdate(Context context,
    				AppWidgetManager appWidgetManager, int[] appWidgetIds)
    			{
    				// 创建RemoteViews对象,加载/res/layout目录下的widget_layout.xml文件
    				RemoteViews rv = new RemoteViews(context.getPackageName(),
    					R.layout.widget_layout);
    				Intent intent = new Intent(context, StackWidgetService.class);
    				// 使用intent更新rv中stack_view组件(StackView)
    				rv.setRemoteAdapter(R.id.stack_view, intent);  //①
    				// 设置当StackWidgetService提供的列表项为空时,直接显示empty_view组件
    				rv.setEmptyView(R.id.stack_view, R.id.empty_view);
    				// 创建启动StackWidgetProvider组件(作为BroadcastReceiver)的Intent
    				Intent toastIntent = new Intent(context,
    					StackWidgetProvider.class);
    				// 为该Intent设置Action属性
    				toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
    				// 将Intent包装成PendingIntent
    				PendingIntent toastPendingIntent = PendingIntent
    					.getBroadcast(context, 0, toastIntent,
    						PendingIntent.FLAG_UPDATE_CURRENT);
    				// 将PendingIntent与stack_view进行关联
    				rv.setPendingIntentTemplate(R.id.stack_view,
    					toastPendingIntent);
    				// 使用AppWidgetManager通过RemteViews更新AppWidgetProvider
    				appWidgetManager.updateAppWidget(
    					new ComponentName(context, StackWidgetProvider.class), rv); //②
    				super.onUpdate(context, appWidgetManager, appWidgetIds);
    			}
    			@Override
    			public void onDeleted(Context context, int[] appWidgetIds)
    			{
    				super.onDeleted(context, appWidgetIds);
    			}
    
    			@Override
    			public void onDisabled(Context context)
    			{
    				super.onDisabled(context);
    			}
    
    			@Override
    			public void onEnabled(Context context)
    			{
    				super.onEnabled(context);
    			}
    			// 重写该方法,将该组件当成BroadcastReceiver使用
    			@Override
    			public void onReceive(Context context, Intent intent)
    			{
    				if (intent.getAction().equals(TOAST_ACTION))
    				{
    					// 获取Intent中的数据
    					int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
    					// 显示Toast提示
    					Toast.makeText(context, "点击第【" + viewIndex + "】个列表项",
    						Toast.LENGTH_SHORT).show();
    				}
    				super.onReceive(context, intent);
    			}    
    		}
    

    widget_layout.xml

    <?xml version="1.0" encoding="utf-8"?>
    			<FrameLayout
    				xmlns:android="http://schemas.android.com/apk/res/android"
    				android:layout_width="match_parent"
    				android:layout_height="match_parent"
    				android:layout_margin="8dp">
    				<StackView
    					android:id="@+id/stack_view"
    					android:layout_width="match_parent"
    					android:layout_height="match_parent"
    					android:gravity="center"
    					android:loopViews="true" />
    				<TextView
    					android:id="@+id/empty_view"
    					android:layout_width="match_parent"
    					android:layout_height="match_parent"
    					android:gravity="center"
    					android:background="#ff0f"
    					android:textColor="#ffffff"
    					android:textStyle="bold"
    					android:text="@string/no_item"
    					android:textSize="20sp" />
    			</FrameLayout>
    

    Manifest.xml

    <?xml version="1.0" encoding="utf-8" ?>
    			
    
        <manifest
        				xmlns:android="http://schemas.android.com/apk/res/android"
        				package="org.crazyit.desktop"
        				android:versionCode="1"
        				android:versionName="1.0">
        				<uses-sdk
        					android:minSdkVersion="14"
        					android:targetSdkVersion="17" />
        			<application
        				android:allowBackup="true"
        				android:label="@string/app_name">
        				<!-- 配置AppWidgetProvider,即配置桌面控件 -->
        				<receiver android:name=".StackWidgetProvider">
        					<!-- 通过该intent-filter指定该Receiver作为桌面控件 -->
        					<intent-filter>
        						<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        					</intent-filter>
        					<!-- 为桌面控件指定meta-data -->
        					<meta-data
        						android:name="android.appwidget.provider"
        						android:resource="@xml/stackwidgetinfo" />
        				</receiver>
        				<!-- 配置RemoteViewsService
        				必须指定权限为android.permission.BIND_REMOTEVIEWS
        				 -->
        				<service
        					android:name=".StackWidgetService"
        					android:permission="android.permission.BIND_REMOTEVIEWS"
        					android:exported="false" />
        			</application>
        			</manifest>
    

    stackwidgetinfo.xml

    <?xml version="1.0" encoding="utf-8"?>
    			<appwidget-provider
    				xmlns:android="http://schemas.android.com/apk/res/android"
    				android:minWidth="110dp"
    				android:minHeight="110dp"
    				android:updatePeriodMillis="3600000"
    				android:previewImage="@drawable/ic_launcher"
    				android:initialLayout="@layout/widget_layout"
    				android:resizeMode="horizontal|vertical"
    				android:autoAdvanceViewId="@id/stack_view">
    			</appwidget-provider>                    
    
    展开全文
  • 首先,什么是桌面widget桌面widget是一种桌面插件,如下图:    这种类型的控件叫做widget,一般长按桌面会弹出一个界面让你选择控件,选择完了拖到桌面就能使用了。 下面我们为这个app来添加一个widget,...

    首先,什么是桌面widget,桌面widget是一种桌面插件,如下图: 

     

    这种类型的控件叫做widget,一般长按桌面会弹出一个界面让你选择控件,选择完了拖到桌面就能使用了。

    下面我们为这个app来添加一个widget,先看一下效果吧。 

     

     

    然后点击这个桌面widget,让他跳转到我们的app里面

     

    怎么样,效果还不错吧?

    下面重点讲一下实现widget的主要步骤: 
    1. 在AndroidManifest.xml里面定义声明 AppWidgetProvider 
    2. 初始化AppWidget的xml文件(信息) 
    3. 实现AppWidget的布局 
    4. 继承 AppWidgetProvider 类,实现具体的 Widget 业务逻辑。

     

    1、在AndroidManifest.xml里面定义声明 AppWidgetProvider

    <receiver android:name=".widget.RecyclerWidgetProvider">
        <intent-filter>
           <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
             android:name="android.appwidget.provider"
             android:resource="@xml/widget_info" />
    </receiver>

    <intent-filter>中必须要包含 APPWIDGET_UPDATE 这个 <action>,所有 Widget 的 broadcast 都是通过这个 filter 来接收的。<meta-data> 声明了 Widget的xml 信息,用的是 xml 目录下的 widget_info.xml。

     

    2、widget_info.xml

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="250dp"
        android:minHeight="170dp"
        android:updatePeriodMillis="0"
        android:previewImage="@drawable/widget_preview"
        android:initialLayout="@layout/widget_layout"
        android:resizeMode="horizontal|vertical"
        android:widgetCategory="keyguard|home_screen">
    </appwidget-provider>

    记住,这个文件不是widget的布局,而是widget的信息,描述了widget的宽高、刷新时间等等信息。 
    minWidth & minHeight:定义了 Widget 的最小宽高,当 minWidth 和 minHeight 不是桌面 cell 的整数倍时,Widget 的宽高会被阔至与其最接近的 cells 大小。Google 官方给出了一个大致估算 minWidth & minHeight 的公式,根据 Widget 所占的 cell 数量来计算宽高:70 × n − 30,n 是所占的 cell 数量。

    updatePeriodMillis: 定义了 Widget 的刷新频率,也就是 App Widget Framework 多久请求一次 AppWidgetProvider 的 onUpdate() 回调函数。但是,系统默认最小更新时间是30分钟,如果这里定义的时间小于30分钟,那么刷新时间还是30分钟。

    previewImage:widget的预览图,就是我们widget列表里面那些预览图

    initialLayout: 这里定义的才是widget的布局

    resizeMode:Widget 在水平和垂直方向是否可以调整大小,值可以为:horizontal(水平方向可以调整大小),vertical(垂直方向可以调整大小),none(不可以调整大小),也可以 horizontal|vertical 组合表示水平和垂直方向均可以调整大小。

    widgetCategory:表示 Widget 可以显示的位置,包括 home_screen(桌面),keyboard(锁屏),keyboard 属性需要 5.0 或以上 Android 版本才可以。

     

    3、AppWidget的布局 
    这里布局可以随便写,我简单的写了个ImageView

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <ImageView
            android:id="@+id/iv_widget"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/widget_preview"
            android:layout_centerHorizontal="true"/>
    </RelativeLayout>

     

    4、继承 AppWidgetProvider 

    public class RecyclerWidgetProvider extends AppWidgetProvider {
        public RecyclerWidgetProvider() {
            super();
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            super.onReceive(context, intent);
        }
    
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            super.onUpdate(context, appWidgetManager, appWidgetIds);
            Log.i("shenlong", "onUpdate");
            for (int i = 0; i < appWidgetIds.length; i++) {
                int appWidgetId = appWidgetIds[i];
                Log.i("shenlong", "onUpdate appWidgetId=" + appWidgetId);
                Intent intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
                        Intent.FLAG_ACTIVITY_TASK_ON_HOME);
                intent.setClass(context, MainActivity.class);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    
                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
                views.setOnClickPendingIntent(R.id.iv_widget, pendingIntent);
    
                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
        }
    
    
        /**
         * 当 Widget 被删除时调用该方法。
         *
         * @param context
         * @param appWidgetIds
         */
        @Override
        public void onDeleted(Context context, int[] appWidgetIds) {
            super.onDeleted(context, appWidgetIds);
            Toast.makeText(context, "onDeleted", Toast.LENGTH_SHORT).show();
        }
    
        /**
         * 当 Widget 第一次被添加时调用,例如用户添加了两个你的 Widget,那么只有在添加第一个 Widget 时该方法会被调用。
         * 所以该方法比较适合执行你所有 Widgets 只需进行一次的操作
         *
         * @param context
         */
        @Override
        public void onEnabled(Context context) {
            super.onEnabled(context);
        }
    
        /**
         * 与 onEnabled 恰好相反,当你的最后一个 Widget 被删除时调用该方法,所以这里用来清理之前在 onEnabled() 中进行的操作。
         *
         * @param context
         */
        @Override
        public void onDisabled(Context context) {
            super.onDisabled(context);
        }
    
        /**
         * 当 Widget 第一次被添加或者大小发生变化时调用该方法,可以在此控制 Widget 元素的显示和隐藏。
         *
         * @param context
         * @param appWidgetManager
         * @param appWidgetId
         * @param newOptions
         */
        @Override
        public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
            super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        }
    
        @Override
        public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
            super.onRestored(context, oldWidgetIds, newWidgetIds);
        }
    }

    注释写的应该还蛮详细的, 
    onUpdateonDeletedonDisabledonAppWidgetOptionsChanged等函数的调用时机都写在注释里了。另外,AppWidgetProvider 继承自 BroadcastReceiver,所以要实现onReceive()方法, onReceive() 中处理的是 Widget 相关的广播事件,然后分发到各个回调函数中onUpdate()onDeleted()onEnabled()onDisabledonAppWidgetOptionsChanged()

     

    5、为widget添加点击事件

    a、首先先定义个开启Activity的intent

    Intent intent = new Intent();

     

    b、用intent实例化一个PendingIntent,调用pendingIntent的getActicity方法来启动另一个Activity

    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

     

    c、实例化RemoteView,其对应相应的Widget布局

    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

     

    d、给RemoteView上的控件设置按钮事件

    views.setOnClickPendingIntent(R.id.iv_widget, pendingIntent);

     

    e、更新AppWidget界面

    appWidgetManager.updateAppWidget(appWidgetId, views);

     

    这样,就实现了点击事件,效果图可以见上图

    源码:https://github.com/AdleyLong/RecyclerViewDemo

     

    转: https://blog.csdn.net/Picasso_L/article/details/70597609

    展开全文
  • 前言:这将是这个系列的最后一篇了,我写这几篇文章也是累的快不行了,再写就真的要吐了,言归正转,前面三篇已经把widget中涉及到的基本知识基本上讲完了,今天我们就做一个小例子,看看桌面...3、《桌面widget详解...

    前言:这将是这个系列的最后一篇了,我写这几篇文章也是累的快不行了,再写就真的要吐了,言归正转,前面三篇已经把widget中涉及到的基本知识基本上讲完了,今天我们就做一个小例子,看看桌面音乐播放器widget是怎么做出来的。

     

    相关文章:

    1、《桌面widget详解(一)——基本demo构建》

    2、《桌面widget详解(二)—— 基本的与service通信》

    3、《桌面widget详解(三)——桌面widget中的控件交互方法》

    4、《桌面widget详解(四)——桌面音乐播放器(实战)》

     

    先看看本篇的最终效果:

     

    这一篇是建立在《桌面widget详解(三)——桌面widget中的控件交互方法》《桌面widget详解(二)—— 基本的与service通信》 的基础之上,我们这里需要用到service通信的基础知识,有关按钮Service播放歌曲的东东已经在《桌面widget详解(二)—— 基本的与service通信》 里讲过了,这里我只是给大家简单回忆一下代码,不会再细讲,如果有不明白的地方翻翻这两篇博客,这篇以实际实现为主。

     

    一、Service控制播放部分(MusicManageService.java)

    首先由于我们要与按钮相交互,所以在Service中的交互一般是通过BroadcastReceiver来实现的,所以在MusicManageService的OnCreate函数中(Service起来的时候调用OnCreate)应该包括下面几个步骤:注册Receiver,初始化歌曲播放列表,开始播放默认歌曲;

    所以首先是注册Receiver:

     

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(ACTION);
    registerReceiver(receiver, intentFilter);

    对应的BroadcastReceiver receiver主要是根据接收来的消息来上一首,下一首,暂停、播放歌曲:

     

     

    public static String ACTION = "to_service";
    public static String KEY_USR_ACTION = "key_usr_action";
    public static final int ACTION_PRE = 0, ACTION_PLAY_PAUSE = 1, ACTION_NEXT = 2;
    private boolean mPlayState = false;
    
    private BroadcastReceiver receiver = new BroadcastReceiver() {
    	@Override
    	public void onReceive(Context context, Intent intent) {
    		String action  = intent.getAction();
    		if (ACTION.equals(action)) {
    			int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);
    			
    			switch (widget_action) {
    			case ACTION_PRE:
    				playPrev(context);
    				Log.d("harvic","action_prev");
    				break;
    			case ACTION_PLAY_PAUSE:
    				if (mPlayState) {
    					pause(context);
    					Log.d("harvic","action_pause");
    				}else{
    					play(context);
    					Log.d("harvic","action_play");
    				}	
    				break;
    			case ACTION_NEXT:
    				playNext(context);
    				Log.d("harvic","action_next");
    				break;
    			default:
    				break;
    			}
    		}
    	}
    };

    然后是初始化播放列表:

     

     

    private int[] mArrayList = new int[9];

     

     

    private void initList() {
    	mArrayList[0] = R.raw.dui_ni_ai_bu_wan;
    	mArrayList[1] = R.raw.fei_yu;
    	mArrayList[2] = R.raw.gu_xiang_de_yun;
    	mArrayList[3] = R.raw.hen_ai_hen_ai_ni;
    	mArrayList[4] = R.raw.new_day;
    	mArrayList[5] = R.raw.shi_jian_li_de_hua;
    	mArrayList[6] = R.raw.ye_gui_ren;
    	mArrayList[7] = R.raw.yesterday_once_more;
    	mArrayList[8] = R.raw.zai_lu_shang;
    }

    最后在Service起来时就应该让它播放歌曲:

     

     

    private void mediaPlayerStart(){
    	mPlayer = new MediaPlayer();
    	mPlayer = MediaPlayer.create(getApplicationContext(), mArrayList[mIndex]);
    	mPlayer.start();
    	mPlayState = true;
    }

    上面就基本上就是MusicManageService的骨架了,其它就是上一首,下一首,播放、暂停,这些难度都不大,就不细讲了,完整的MusicManageService.java代码如下:

     

     

    public class MusicManageService extends Service {
    
    	private MediaPlayer mPlayer;
    	private int mIndex = 4;// 从中间开始放
    	private int[] mArrayList = new int[9];
    	public static String ACTION = "to_service";
    	public static String KEY_USR_ACTION = "key_usr_action";
    	public static final int ACTION_PRE = 0, ACTION_PLAY_PAUSE = 1, ACTION_NEXT = 2;
    	private boolean mPlayState = false;
    
    	private BroadcastReceiver receiver = new BroadcastReceiver() {
    		@Override
    		public void onReceive(Context context, Intent intent) {
    			String action  = intent.getAction();
    			if (ACTION.equals(action)) {
    				int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);
    				
    				switch (widget_action) {
    				case ACTION_PRE:
    					playPrev(context);
    					Log.d("harvic","action_prev");
    					break;
    				case ACTION_PLAY_PAUSE:
    					if (mPlayState) {
    						pause(context);
    						Log.d("harvic","action_pause");
    					}else{
    						play(context);
    						Log.d("harvic","action_play");
    					}	
    					break;
    				case ACTION_NEXT:
    					playNext(context);
    					Log.d("harvic","action_next");
    					break;
    				default:
    					break;
    				}
    			}
    		}
    	};
    
    	@Override
    	public IBinder onBind(Intent intent) {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public void onCreate() {
    		// TODO Auto-generated method stub
    		super.onCreate();
    
    		IntentFilter intentFilter = new IntentFilter();
    		intentFilter.addAction(ACTION);
    		registerReceiver(receiver, intentFilter);
    
    		initList();
    
    		mediaPlayerStart();		
    	}
    	private void mediaPlayerStart(){
    		mPlayer = new MediaPlayer();
    		mPlayer = MediaPlayer.create(getApplicationContext(), mArrayList[mIndex]);
    		mPlayer.start();
    		mPlayState = true;
    	}
    
    	private void initList() {
    		mArrayList[0] = R.raw.dui_ni_ai_bu_wan;
    		mArrayList[1] = R.raw.fei_yu;
    		mArrayList[2] = R.raw.gu_xiang_de_yun;
    		mArrayList[3] = R.raw.hen_ai_hen_ai_ni;
    		mArrayList[4] = R.raw.new_day;
    		mArrayList[5] = R.raw.shi_jian_li_de_hua;
    		mArrayList[6] = R.raw.ye_gui_ren;
    		mArrayList[7] = R.raw.yesterday_once_more;
    		mArrayList[8] = R.raw.zai_lu_shang;
    	}
    
    	@Override
    	public int onStartCommand(Intent intent, int flags, int startId) {
    
    		return super.onStartCommand(intent, flags, startId);
    	}
    
    	@Override
    	public void onDestroy() {
    
    		super.onDestroy();
    		mPlayer.stop();
    	}
    
    	/**
    	 * 播放下一首
    	 * 
    	 * @param context
    	 */
    	public void playNext(Context context) {
    		if (++mIndex > 8) {
    			mIndex = 0;
    		}
    		mPlayState = true;
    		playSong(context, mArrayList[mIndex]);
    	}
    
    	/**
    	 * 播放上一首
    	 * 
    	 * @param context
    	 */
    	public void playPrev(Context context) {
    		if (--mIndex < 0) {
    			mIndex = 8;
    		}
    		mPlayState = true;
    		playSong(context, mArrayList[mIndex]);
    	}
    
    	/*
    	 * 继续播放
    	 */
    	public void play(Context context) {
    		mPlayState = true;
    		mPlayer.start();
    	}
    
    	/**
    	 * 暂停播放
    	 * 
    	 * @param context
    	 */
    	public void pause(Context context) {
    		mPlayState = false;
    		mPlayer.pause();		
    	}
    
    	/**
    	 * 播放指定的歌曲
    	 * 
    	 * @param context
    	 * @param resid
    	 */
    	private void playSong(Context context, int resid) {
    		AssetFileDescriptor afd = context.getResources().openRawResourceFd(
    				mArrayList[mIndex]);
    		try {
    			mPlayer.reset();
    			mPlayer.setDataSource(afd.getFileDescriptor(),
    					afd.getStartOffset(), afd.getDeclaredLength());
    			mPlayer.prepare();
    			mPlayer.start();
    			afd.close();
    		} catch (Exception e) {
    			Log.e("harvic","Unable to play audio queue do to exception: "+ e.getMessage(), e);
    		}
    
    	}
    }

    二、widget发送广播部分(ExampleAppWidgetProvider.java)

    首先,在用户添加widget时,会调用OnUpdate()函数,所在我们在OnUpdate()中要实现绑定RemoteView和更新Widget的操作。

     

    private void pushUpdate(Context context,AppWidgetManager appWidgetManager) {
    	
    	RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    	//将按钮与点击事件绑定
    	remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(context, R.id.play_pause));
    	remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(context, R.id.prev_song));
    	remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(context, R.id.next_song));
    
    	// 相当于获得所有本程序创建的appwidget
    	ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
    	appWidgetManager.updateAppWidget(componentName, remoteView);
    }
    
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    		int[] appWidgetIds) {
    	
    	pushUpdate(context,appWidgetManager);
    }

    首先是新建RemoteView,并将它与那三个按钮相绑定,其中的GetPendingIntent的实现与上一篇一样,也是把按钮ID传进去,通过Uri来传送,这里为了接收到以后方便识别是按钮点击传过去的消息,我们随便加一个Category字段,所以在接收方只需要通过intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)来判断是不是我们这里传过去的Intent即可,这里又比上篇高级了一点点有没有,哈哈。

     

     

    private PendingIntent getPendingIntent(Context context, int buttonId) {
    	Intent intent = new Intent();
    	intent.setClass(context, ExampleAppWidgetProvider.class);
    	intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
    	intent.setData(Uri.parse("harvic:" + buttonId));
    	PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
    	return pi;
    }

    然后是接收部分,在接收时,首先根据当前用户点击的哪个按钮,然后给MusicManageService发送不同的广播,让MusicManageService做出不同的响应,接收代码如下 :

     

     

    public void onReceive(Context context, Intent intent) {
    	String action = intent.getAction();
    	Log.d("harvic", "action:"+action);
    	
    	if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
    		Uri data = intent.getData();
            int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
            switch (buttonId) {
            case R.id.play_pause:
            	pushAction(context,MusicManageService.ACTION_PLAY_PAUSE);
            	if(mStop){
            		Intent startIntent = new Intent(context,MusicManageService.class);
    				context.startService(startIntent);
            		mStop = false;
            	}
            	break;
            case R.id.prev_song:
            	pushAction(context, MusicManageService.ACTION_PRE);
            	break;
            case R.id.next_song:
            	pushAction(context, MusicManageService.ACTION_NEXT);
            	break;
            }
    
    	}
    	super.onReceive(context, intent);
    }

    在这里首先根据是不是包含intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)这个category来判断是不是点击按钮发出来的消息,然后提取出按钮ID,最后根据不同的按钮ID发送出不同的消息:

     

     

    private void pushAction(Context context, int ACTION) {
        Intent actionIntent = new Intent(MusicManageService.ACTION);
        actionIntent.putExtra(MusicManageService.KEY_USR_ACTION, ACTION);
        context.sendBroadcast(actionIntent);
    }

    这里发送消息与MusicManageService的消息接收方式是统一的,发送和接收都是通过Action来匹配,携带的值是当前的Action,下面就是MusicManageService接收时的部分代码,我再摘一遍,方便大家理解:

     

     

    private BroadcastReceiver receiver = new BroadcastReceiver() {
    	@Override
    	public void onReceive(Context context, Intent intent) {
    		String action  = intent.getAction();
    		if (ACTION.equals(action)) {
    			int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);
    			
    			switch (widget_action) {
    			case ACTION_PRE:
    				break;
    			case ACTION_PLAY_PAUSE:
    				break;
    			case ACTION_NEXT:
    				break;
    			}
    		}
    	}
    };

    所以完整的ExampleAppWidgetProvider代码是这样的:

     

     

    public class ExampleAppWidgetProvider extends AppWidgetProvider {
    
    	private ExampleAppWidgetProvider mProvider = null;
    	private boolean mStop = true;
    	
    	private PendingIntent getPendingIntent(Context context, int buttonId) {
    		Intent intent = new Intent();
    		intent.setClass(context, ExampleAppWidgetProvider.class);
    		intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
    		intent.setData(Uri.parse("harvic:" + buttonId));
    		PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
    		return pi;
    	}
    	
    	// 更新所有的 widget
    	private void pushUpdate(Context context,AppWidgetManager appWidgetManager) {
    		
    		RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    		//将按钮与点击事件绑定
    		remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(context, R.id.play_pause));
    		remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(context, R.id.prev_song));
    		remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(context, R.id.next_song));
    
    		// 相当于获得所有本程序创建的appwidget
    		ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
    		appWidgetManager.updateAppWidget(componentName, remoteView);
    	}
    
    	@Override
    	public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    			int[] appWidgetIds) {
    		
    		pushUpdate(context,appWidgetManager);
    	}
    	
    	// 接收广播的回调函数
    	@Override
    	public void onReceive(Context context, Intent intent) {
    		String action = intent.getAction();
    		Log.d("harvic", "action:"+action);
    		
    		if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
    			Uri data = intent.getData();
    	        int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
    	        switch (buttonId) {
    	        case R.id.play_pause:
    	        	pushAction(context,MusicManageService.ACTION_PLAY_PAUSE);
    	        	if(mStop){
    	        		Intent startIntent = new Intent(context,MusicManageService.class);
    					context.startService(startIntent);
    	        		mStop = false;
    	        	}
    	        	break;
    	        case R.id.prev_song:
    	        	pushAction(context, MusicManageService.ACTION_PRE);
    	        	break;
    	        case R.id.next_song:
    	        	pushAction(context, MusicManageService.ACTION_NEXT);
    	        	break;
    	        }
    	
    		}
    		super.onReceive(context, intent);
    	}
    	
    	private void pushAction(Context context, int ACTION) {
            Intent actionIntent = new Intent(MusicManageService.ACTION);
            actionIntent.putExtra(MusicManageService.KEY_USR_ACTION, ACTION);
            context.sendBroadcast(actionIntent);
        }
    }

    到这里,效果是这样的:(在模拟器上点击看起来没有任何反应,其实已经在播放歌曲了,上一首,下一首,播放、暂停功能都是可用的)

    这部分源码在文章底部给出

    三、Service反向通知Widget更新当前状态

    上面我们已经实现了widget按钮向Service发广播来播放歌曲的播放、暂停,上一首,下一首,但是我们的widget状态确没有改变,这节我们就需要根据当前歌曲的状态来更新widget控件的状态。

    1、发送当前状态广播

    首先,我们要在MusicManageService中根据当前的播放状态往ExampleAppWidgetProvider发送广播,广播的目的主要是改变当前播放按钮的状态(播放、暂停)还有更新TextView,让它显示当前播放歌曲的ID值。

    所以我们在发送广播时,需要定义Intent的Action,和存放播放状态、歌曲Id的putExtra(key,value)中的key值:

     

    public static String MAIN_UPDATE_UI = "main_activity_update_ui";  //Action
    public static String KEY_MAIN_ACTIVITY_UI_BTN = "main_activity_ui_btn_key"; //putExtra中传送当前播放状态的key
    public static String KEY_MAIN_ACTIVITY_UI_TEXT = "main_activity_ui_text_key"; //putextra中传送TextView的key
    public static final int  VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2;//当前歌曲的播放状态

    发送时:

     

     

    private void postState(Context context, int state,int songid) {
    	Intent actionIntent = new Intent(ExampleAppWidgetProvider.MAIN_UPDATE_UI);
    	actionIntent.putExtra(ExampleAppWidgetProvider.KEY_MAIN_ACTIVITY_UI_BTN,state);
    	actionIntent.putExtra(ExampleAppWidgetProvider.KEY_MAIN_ACTIVITY_UI_TEXT, songid);
    	context.sendBroadcast(actionIntent);
    }

    其中:

     

    state就是上面VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2其中一个状态;

    songid:表示当前播放歌曲的id值

    所以在播放歌曲状态和歌曲id值改变时,就应该发送广播:

    首先,在Service起来时,我们开始播放歌曲:

    private void mediaPlayerStart(){
    	mPlayer = new MediaPlayer();
    	mPlayer = MediaPlayer.create(getApplicationContext(), mArrayList[mIndex]);
    	mPlayer.start();
    	mPlayState = true;
    	postState(getApplicationContext(), ExampleAppWidgetProvider.VAL_UPDATE_UI_PLAY,mIndex);
    }

    同样,在上一首,下一首,播放,暂停时都要发送广播:

     

    播放时:(改变了播放状态)

    public void play(Context context) {
    	……
    	postState(context, ExampleAppWidgetProvider.VAL_UPDATE_UI_PLAY,mIndex);
    }

    暂停时:(改变了播放状态)

    public void pause(Context context) {
    	……		
    	postState(context, ExampleAppWidgetProvider.VAL_UPDATE_UI_PAUSE,mIndex);
    }

    上一首:(改变了歌曲ID)

    public void playPrev(Context context) {
    	……
    	postState(context, ExampleAppWidgetProvider.VAL_UPDATE_UI_PLAY,mIndex);
    }

    下一首:(改变了歌曲ID)

    public void playNext(Context context) {
    	……
    	postState(context, ExampleAppWidgetProvider.VAL_UPDATE_UI_PLAY,mIndex);
    }

    OK啦,到这里发送就结束了,下面就是接收了。
     

    2、接收广播

     

    首先在接收广播之前要注册,ExampleAppWidgetProvider以前说过是直接派生自 BroadcastReceiver的,所有我们只能采用静态注册的方式:注意的action要与发送的一致,即:main_activity_update_ui

     

            <receiver android:name=".ExampleAppWidgetProvider" >
                <intent-filter>
    				<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    				<action android:name="main_activity_update_ui" />
    			</intent-filter>
    			<meta-data android:name="android.appwidget.provider"
    				android:resource="@xml/example_appwidget_info" />
            </receiver>

    在上面,我们在ExampleAppWidgetProvider中更新RemoteView的pushUpdate() 的代码是这样的:

     

    private void pushUpdate(Context context,AppWidgetManager appWidgetManager) {
    	
    	RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    	//将按钮与点击事件绑定
    	remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(context, R.id.play_pause));
    	remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(context, R.id.prev_song));
    	remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(context, R.id.next_song));
    
    	// 相当于获得所有本程序创建的appwidget
    	ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
    	appWidgetManager.updateAppWidget(componentName, remoteView);
    }

    在这里,我们只是绑定了三个按钮控件,但并没有更新当前播放按钮状态和TextView上的字体,所以我们对它加以更改,在绑定按钮以后,根据当前接收到的状态,更新RemoteView

     

    private void pushUpdate(Context context,AppWidgetManager appWidgetManager,String songName,Boolean play_pause) {
    	
    	RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
    	//将按钮与点击事件绑定
    	remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(context, R.id.play_pause));
    	remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(context, R.id.prev_song));
    	remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(context, R.id.next_song));
    	
    	//设置内容
    	if (!songName.equals("")) {
    		remoteView.setTextViewText(R.id.song_name, songName);
    	}
    	//设定按钮图片
    	if (play_pause) {
    		remoteView.setImageViewResource(R.id.play_pause, R.drawable.car_musiccard_pause);
    	}else {
    		remoteView.setImageViewResource(R.id.play_pause, R.drawable.car_musiccard_play);
    	}
    	// 相当于获得所有本程序创建的appwidget
    	ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
    	appWidgetManager.updateAppWidget(componentName, remoteView);
    }

    其中songName存储接收过来的歌曲id值,play_pause表示当前歌曲的播放状态,根据当前的播放状态加载不同的播放状态图片;

     

    在理解了上面的更新RemoteView的部分以后,下面看看接收广播的代码:

     

    public void onReceive(Context context, Intent intent) {
    	String action = intent.getAction();
    	Log.d("harvic", "action:"+action);
    	
    	if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
    		…… //接收到的按钮点击广播的处理部分
    	}else if (MAIN_UPDATE_UI.equals(action)){
    			int play_pause =  intent.getIntExtra(KEY_MAIN_ACTIVITY_UI_BTN, -1);
    			int songid = intent.getIntExtra(KEY_MAIN_ACTIVITY_UI_TEXT, -1);
    			switch (play_pause) {
    			case VAL_UPDATE_UI_PLAY:
    				pushUpdate(context, AppWidgetManager.getInstance(context), "current sond id:"+songid,true);
    				break;
    			case VAL_UPDATE_UI_PAUSE:
    				pushUpdate(context, AppWidgetManager.getInstance(context), "current sond id:"+songid,false);
    				break;
    			default:
    				break;
    			}
    			
    		}
    
    	super.onReceive(context, intent);
    }

    首先,我们通过获取action值来判断当前是不是MusicManageService传过来的消息,然后得到传过来当前的播放状态和歌曲ID值,然后利用pushUpdate更新RemoteView;

     

    四、附:相关问题

    下面记录一下,我在实际开发中遇到的问题,分享给大家

    1、有关RemoteViews实例复用(绝对要每次新建RemoteView实例)

     在实际项目中,大家可能会想到复用remoteView,即如果已经创建了就不再重新加载layout,而是重新绑定控件及数据
     !!!!!!!千万不要这样!!!!!!!一定要每次都要新建remoteView!!!!!这是血和泪的教训!!!!!
     因为:如果你在绑定数据时涉及图片等大数据,remoteView不会每次清理,所以如果每次都使用同一个remoteView进行传输会因为溢出而绐终无响应!!!! 你看着每次动作都通过updateAppWidget传送出去了,但界面死活就是没响应;而且重装应用程序也不会有任何反应,只能重启手机才会重新有反应,也是醉了。
     主要原因在于:Binder data size limit is 512k,由于传输到appWidget进程中的Binder最大数据量是512K,并且RemoteView也不会每次清理, 所以如果每次都使用同一个RemoteView进行传输会因为溢出而报错.所以必须每次重新建一个RemoteView来传输!!!!!!

    2、操作RemoteView中控件的方法

    在RemoteView中的操作控件的方法非常有限,但我们的需求确是非常多样的,所以怎样才能像操作平常控件一样多样性的操作RemoteView呢,

    举例:
    如果我们需要把widget中的一个view临时隐藏,我们可以这样调用:remoteviews.setInt(textviewid,"setVisibility",VIEW.INVISIBLE);

    更多看这里:《Android App Widget中如何调用RemoteView中的函数》

     

    OK啦,终于写完了,有点要累尿了,这部分涉及到的代码量太大,我上面讲的也不太详细,大家匹配代码再仔细琢磨琢磨一下吧。

     

    参考文章:

    1、《Android 之窗口小部件详解--App Widget》  (初步入门级,写的很好)

    2、《Android桌面组件AppWidget讲解》

    3、《app widget 进入主客户端代码》  (讲述了,点击桌面widget如何进入主app的代码)

    4、《AppWidget基础小结》

    5、《Android 桌面组件【app widget】 进阶项目--心情记录器》

    6、《Android Widget开发的相关技术点记录》 (其中有:存储widgetID,以防app崩溃后,无法更新widget的问题)

    7、《android widget开发点滴》

    8、《Android Appwidget 之按钮事件》

    9、《android 转载 widget点击事件》

    10、《 Android基础之AppWidgetProvider》

    11、《android之widget详解》

     

    源码包含两部分内容:

    1、《初步实现》:这是第二部分结束时对应的源码,还没有加上service反向通知更新RemoteView代码之前的代码;

    2、《完整实现》:这是本篇的完整实现代码;

     

    如果我的文章有帮到你,记得关注哦

    源码地址:http://download.csdn.net/detail/harvic880925/8232951

    请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/41754477  谢谢!

     

    如果你喜欢我的文章,你可能更喜欢我的公众号

    启舰杂谈

     

    展开全文
  • 这篇文章开始, 我会和大家一起对 Xcode 自带的默认项目做一些优化, 让大家更加深入的学习桌面 Widget.如果需要了解上一篇内容, 可以查看iOS 14 Widget 上手体验.为了提起大家的兴趣, 我先展示一下完成这篇教程后, ...
  • 桌面widget的大小

    2014-07-07 11:29:58
    桌面widget大小一般都是4*4,或者4*2,关于这个大小的确定,我们是需要在配置文件里面写的,如下  android:minWidth="294dip"  android:minHeight="72dip"  android:initialLayout="@layout/widget"  >
  • 博客《桌面widget详解(四)——桌面音乐播放器(实战)》对应源码,博客地址:http://blog.csdn.net/harvic880925/article/details/41754477
  • 最近需要编写一个日期时间的桌面Widget用来关联日历程序,以前很少写桌面Widget。对这方面技术不是很熟悉,今天花时间重新整理了一下,顺便把编写一个简单时间日期程序过程记录下来。 桌面Widget其实就是一个显示...
  • 最近需要编写一个日期时间的桌面Widget用来关联日历程序,以前很少写桌面Widget。对这方面技术不是很熟悉,今天花时间重新整理了一下,顺便把编写一个简单时间日期程序过程记录下来。 桌面Widget其实就是一个显示...
  • 前言:发现只有让自己忙起来,内心才会感到舒畅,才没有罪恶感,男人好像也有大姨妈,一个月总想懒那么几天,懒到极限的...3、《桌面widget详解(三)——桌面widget中的控件交互方法》 4、《桌面widget详解(四...
  • android天气预报的桌面widget

    热门讨论 2009-11-04 14:04:41
    最近在研究桌面widget,弄到了一个天气预报,所以和大家一起分享
  • [Android]桌面Widget动态刷新

    千次阅读 2017-06-06 06:25:38
    [Android]桌面Widget动态刷新 2012-08-03 0 个评论  收藏 我要投稿 关于桌面widget的动态刷新有这样一个配置, 在res/xml下下有一个属性 android:updatePeriodMillis="86400000" ...
  • andriod的一个桌面Widget 一个MP3播放器

    热门讨论 2012-12-26 18:47:57
    这是一个MP3程序,这个程序不仅仅带Activity,还有一个桌面Widget,同时是可以运行的源代码。可以通过桌面Widget控制后台音乐的播放,也可以点击Widget上面的LOGO进入应用程序。 涉及到了,一个基本的widget程序的大...
  • 的来说,widget主要功能就是显示...最近需要编写一个日期时间的桌面Widget用来关联日历程序,以前很少写桌面Widget。对这方面技术不是很熟悉,今天花时间重新整理了一下,顺便把编写一个简单时间日期程序过程记录下来
  • 现在手上的项目有一个这样的需求,就是系统一开机的时候展示桌面指定的Widget (已经实现了,搞了好久),现在又有了新的需求,就是需要实现桌面Widget的大小与桌面同宽,求各位大神指点,拜托拜托
  • 由于要编写一个天气预报应用需要用到桌面widget配套使用,所以查了不少资料,终于知道了如何实现。 这是我找到的一个比较清晰的教程博文http://blog.csdn.net/dyllove98/article/details/9280847。 这是效果图: ...
  • 开发安卓桌面widget

    2019-11-18 10:49:49
    项目中有时需要在安卓桌面添加widget,方便用户查看或做一些操作. Demo实现效果图 用于实现一个显示日期的widget 实现步骤 1. 新建widget 2. 选择占用屏幕空间的大小 3. 自动生成的代码文件 4. 主要代码如下: ...
  • 如题,在客户端软件中我是计算每个课程格子的大小,用代码动态生成textview实现的。但是做桌面widget开发中发现要用remoteview实现界面,但remoteview好像不支持动态生成textview,问一下各位有没有什么思路 ?

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,968
精华内容 787
关键字:

桌面widget