[sixinice 原创翻译]安卓屏幕小配件(挂件)开发资料

Discussion in 'Chinese Forum' started by 52manhua, Oct 13, 2017.

  1. 52manhua

    52manhua Member Licensed User

    安卓屏幕小配件(挂件)开发资料

    原帖第一部分:https://www.b4x.com/android/forum/threads/10166
    原帖第二部分:https://www.b4x.com/android/forum/threads/10356
    翻译贴地址: http://www.sixinice.pw/drupal/node/67

    第一部分

    Basic4android v1.6 以上版本开始支持屏幕小配件开发. 这个材料会告诉你如何构建你自己的屏幕小配件(又称为 应用小配件 或者 '外挂'?).

    理解这一点是很重要的:小配件是建立和运行在一个特殊的进程中, 和你原来的程序运行在不同的进程内. 主屏幕的启动管理程序在管理你的小配件应用.
    这就意味着,你没办法直接对小配件的控件进行处理. 作为替代,我们使用一种特殊对象 RemoteViews 来间接操纵小配件的控件.

    小配件类应用并不支持所有的控件类型. 以下几种控件是可以包含在小控件内的(译者注:Android 3.0 版本似乎开始支持 listview,但是 b4a 尚未提供支持):

    - Button (default drawable/默认 或者 drawable 模式)
    - Label (ColorDrawable 或者 GradientDrawable/渐变 模式 )
    - Panel (ColorDrawable 或者 GradientDrawable/渐变 模式)
    - ImageView
    - ProgressBar (both modes/全部模式)

    所有的控件都仅支持单击事件.

    小配件的布局和设置必须在 XML 文件中实现. 在编译过程中 Basic4android 读取被设计器建立的布局文件,生成需要的 XML 文件.

    每一个配件都需要一个响应的服务模块与之匹配. 通过这个服务模块,建立小配件和更新配件状态.

    建立小配件 - 按步骤如下:

    - 建立一个服务模块(1). [注意] 管理小配件的服务模块也是一个标准的服务.
    - 在设计器中设计小配件的布局(2). 首先添加一个 Panel 然后把其他控件放到这个 Panel 上.
    小配件的布局会以 panel 为基础生成.
    - 在小配件服务模块中添加以下代码(3):
    Code:
    Sub Process_Globals
         
    Dim rv As RemoteViews
         
    End Sub
       
         
    Sub Service_Create
         rv = 
    ConfigureHomeWidget("LayoutFile""rv"0"Widget Name")
         
    End Sub
       
         
    Sub Service_Start (StartingIntent As Intent)
         
    If rv.HandleWidgetEvents(StartingIntent) Then Return
         
    End Sub
       
         
    Sub rv_RequestUpdate
         rv.UpdateWidget
         
    End Sub
       
         
    Sub rv_Disabled
         
    StopService("")
         
    End Sub
       
         
    Sub Service_Destroy
       
         
    End Sub
    - 编译并运行你的应用(4). 在手机主界面中, 长按空白处你就会看到你的小配件应用会显示在列表中(译者注: 在不同手机系统中添加桌面配件的过程可能不同).

    ConfigureHomeWidget 是一个特殊关键词. 在运行的时候,通过布局文件建立 RemoteViews 对象,初始化事件. 在编译时,编译器根据它的设置,生成需要的文件.
    四个参数分别是: 布局文件, 事件名, 更新间隔和配件名称.
    通过事件名设置管理 RequestUpdate 和 Disabled 事件.
    小配件能被设置成自动更新. 更新间隔, 以分钟为基本单位, 定义了小配件发生更新的时间. 设置为 0 来禁止自动更新. 更新过于频繁会增加电池的损耗. 最小的更新值是 30 分钟.
    配件名 - 出现在桌面管理程序中的配件名称.

    因为所有的参数都是要传入编译器的, 只接受字符串和数字格式.

    事件:
    Code:
    Sub Service_Start (StartingIntent As Intent)
         
    If rv.HandleWidgetEvents(StartingIntent) Then Return
         
    End Sub
    上面这段代码检查让配件服务启动的进程的消息 ,返回启动的小配件的信息. 如果配件服务顺利启动就会返回 TRUE.
    小配件会产生两个事件. RequestUpdate 事件在小配件进行更新的时候被激活. 在放置小配件,系统重启,自动更新,应用程序更新的时候会被触发.
    禁用的事件会在小配件从屏幕上移除的时候发生.

    如同前面说过的一样,所有的控件都仅支持单击的方法. (举个例子)为一个按钮添加单击事件:比如 Button1 ,就增加一个子过程 Button1_Click(子过程的名称应该和配置中事件名称保持一致).
    举例,如果你想在按下 Button1 的时候打开 main Acttivity,写入如下代码:
    Code:
    Sub Button1_Click
         
    StartActivity(Main)
         
    End Sub
    这样来修改小配件:
    不可能直接修改小配件的控件. 作为替代,我们使用 RemoteView.Set 方法.
    如果我们想要修改一个名为 Label1 的标签的文本,我们可以使用以下这段代码:
    Code:
    `rv.SetText("Label1""This is the new text.")`
    其他代码
    Code:
    rv.UpdateWidget
    在进行修改之后,我们使用 rv.UpdateWidget 来更新小配件.

    一个简单的例子在附件里.
    这个例子添加了一个简单的小配件. 虽然如此,只作为演示,无任何实用性.
    [​IMG]

    本材料的第二段: http://www.basic4ppc.com/forum/basi...oid-home-screen-widgets-tutorial-part-ii.html

    [注意] 推荐在 Release mode 运行小配件类应用(使用调试模式无法运行小配件类程序).


    第二部分

    如果没有读过第一部分请向上阅读.
    在这里我们来建立一个 "每日格言" 的小配件.

    [​IMG]

    开始布局. 这个小配件由一个文本标签和一个包含箭头图片的图片控件组成.

    设计器截图:
    [​IMG]

    通过上面的图片,你会发现我们使用了两个 panel. 第一层 panel 叫做 pnlBase 是一个透明的 panel (Alpha=0). 这个基础的 panel 包含另一个 panel,即一个灰色的 panel.
    使用透明 panel 的目的是为了和其他控件留出一些间隔. 小配件的尺寸是由基础 panel 所决定的. 没有这个透明层(panel) 配件就不会跟其他配件或者屏幕左端保留间隔.

    我们设置的基础 panel 的尺寸是 294x72. 这是一个 4x1 标准的小配件的推荐尺寸.
    提示: 在一些情况下,你虽然改变布局, 但是因为这个小配件已经存在,小配件不会被更新. 删除这个小配件然后重新添加就能看到改变了.

    现在是程序的逻辑问题.
    程序每天从 5 个 Famous Quotes at BrainyQuote 的信息源获取 20 条格言,第一条格言会被显示出来. 每次用户按下小配件按钮时候,会显示下一条格言.
    在获得格言的同时,每个信息源的第一条格言被添加到格言列表的开头。. 只有每一个feed的第一条格言是新的时候,我们从新的格言开始。.
    使用 HttpUtils 下载信息源,使用 XmlSax 库进行解析. 查看代码获得更多信息.

    这个小配件被设置成每 24 小时自动更新. 使用以下这条语句实现:
    Code:
    '配置小配件,设成每 24 小时更新一次 (1440 分钟).

      rv = 
    ConfigureHomeWidget("WidgetLayout""rv"1440"Quote of the day")

    24 小时后或者第一次添加或者重启后 RequestUpdate 事件被触发.
    Code:

       
    Sub rv_RequestUpdate
        quotes.Clear
        currentQuote = -
    1
        HttpUtils.DownloadList(
    "Quotes"Array As String("http://feeds.feedburner.com/brainyquote/QUOTEBR", _
         
    "http://feeds.feedburner.com/brainyquote/QUOTEAR""http://feeds.feedburner.com/brainyquote/QUOTEFU", _
         
    "http://feeds.feedburner.com/brainyquote/QUOTELO""http://feeds.feedburner.com/brainyquote/QUOTENA"))
       
    End Sub
    首先我们清除以前的格言,获得新的格言. [注意]如果手机(设备)正处于睡眠(关屏)状态,很可能会失败(因为很多设备都会在睡眠状态关闭 wifi). 这种情况下,新的格言会在用户按下按钮的时候才获取.
    这种情况下,你不能寄希望于自动更新,确保有其他方式在用户操作的时候能获得正确的数据.

    暂时保留原来的数据. 在小配件中运行的代码不会总是处于运行状态. 系统随时可能关闭小配件的进程.
    所以别把变量作为全局变量存储在这些代码中.
    所有的需要存储的变量最后到写入一个文件中.
    RandomAccessFile.WriteObject 和 ReadObject 能胜任这样的任务.
    每次小配件发送一个请求的时候, Service_Start 就会被触发.
    这个过程并没有很多要做的事情:
    Code:
    Sub Service_Start (StartingIntent As Intent)
        
    If rv.HandleWidgetEvents(StartingIntent) Then Return
       
    End Sub
    这段代码让 RemoteViews激活正确的事件.

    但是,我们的进程没有被激活的情况下,Service_Create 就会被触发. Service_Create 是一个重要的入口, 它能帮助我们读取前一个保存的状态.
    Code:
    Sub Service_Create
        
    '配置小配件,设成每 24 小时更新一次 (1440 分钟).
        rv = ConfigureHomeWidget("WidgetLayout""rv"1440"Quote of the day")
        HttpUtils.CallbackActivity = 
    "WidgetService"
        HttpUtils.CallbackUrlDoneSub = 
    "UrlDone"
        HttpUtils.CallbackJobDoneSub = 
    "JobDone"
        parser.Initialize
         
        
    '如果保存有前一个状态的就进行载入.
        '这适用于我们的进程被结束的时候,用户刚好按下小配件的情况.
        If File.Exists(File.DirInternalCache, QUOTES_FILE) Then
         raf.Initialize(
    File.DirInternalCache, QUOTES_FILE, True)
         quotes = raf.ReadObject(
    0)
         raf.Close
        
    Else
         quotes.Initialize
        
    End If
        
    If File.Exists(File.DirInternalCache, CURRENTQUOTE_FILE) Then
         currentQuote = 
    File.ReadString(File.DirInternalCache, CURRENTQUOTE_FILE)
        
    End If
       
    End Sub
    这个工程在附件中.
     
Loading...