Advertisement
  1. Code
  2. Mobile Development
  3. Android Development

Viết một Widget cho Ứng dụng Android của bạn: Cập nhật Widget

Scroll to top
Read Time: 15 min

() translation by (you can also view the original English article)

Widget của ứng dụng cung cấp cho người dùng của bạn khả năng truy cập dễ dàng đến các tính năng được sử dụng thường xuyên nhất của ứng dụng, đồng thời đưa ứng dụng của bạn hiện diện trên màn hình home của người dùng. Bằng cách thêm một widget vào dự án, bạn có thể cung cấp trải nghiệm người dùng tốt hơn, đồng thời khuyến khích người dùng tiếp tục sử dụng ứng dụng của bạn, bởi vì mỗi khi họ lướt qua màn hình home, họ sẽ nhìn thấy widget của bạn, hiển thị một số thông tin hữu ích và thú vị nhất của ứng dụng.

Trong loạt bài gồm ba phần này, chúng ta sẽ xây dựng một widget của ứng dụng mà có tất cả các tính năng mà bạn sẽ tìm thấy trong hầu hết mọi ứng dụng Android.

Trong bài viết đầu tiên, chúng ta đã tạo một widget để truy xuất và hiển thị dữ liệu và thực hiện một hành động duy nhất trong phản hồi đối với sự kiện onClick. Tuy nhiên, widget của chúng ta vẫn còn thiếu một tính năng quan trọng: nó không bao giờ cập nhật thông tin mới. Một widget mà không bao giờ có bất cứ điều gì mới mẻ thì không có ích gì cả, vì vậy chúng ta sẽ cung cấp cho widget của chúng ta hai cách khác nhau để cập nhật.

Vào cuối của loạt bài này, chúng ta sẽ mở rộng widget của chúng ta để truy xuất và hiển thị dữ liệu mới một cách tự động dựa trên một lịch biểu sự tương tác với người dùng.

Chúng ta sẽ bắt đầu từ nơi mà chúng ta đã dừng lại, vì vậy nếu bạn không có một bản sao của widget mà chúng ta đã tạo ra trong bài viết đầu tiên, thì bạn có thể tải về từ GitHub.

Cập nhật Layout

Bước đầu tiên là cập nhật layout của chúng ta nhằm hiển thị một số dữ liệu mà thay đổi theo thời gian. Để giúp chúng ta xem chính xác khi nào widget của chúng ta nhận được mỗi bản cập nhật, tôi sẽ làm cho widget truy vấn và hiển thị thời gian hiện tại, bất cứ khi nào nó thực hiện một bản cập nhật.

Tôi sẽ thêm những thứ sau đây vào layout của widget:

  • một TextView hiển thị nhãn Last Update
  • một TextView sẽ hiển thị thời gian của bản cập nhật gần đây nhất

Trong khi chúng ta làm việc với tập tin new_app_widget.xml, tôi cũng sẽ thêm TextView mà cuối cùng sẽ cho phép người dùng kích hoạt một lần cập nhật bằng cách thủ công:

1
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
2
   android:layout_width="match_parent"
3
   android:layout_height="match_parent"
4
   android:padding="@dimen/widget_margin"
5
   android:background="@drawable/widget_background"
6
   android:orientation="vertical" >
7
   
8
   <LinearLayout
9
       android:background="@drawable/tvbackground"
10
       style="@style/widget_views"
11
       android:layout_width="match_parent"
12
       android:layout_height="wrap_content"
13
       android:orientation="horizontal">
14
       
15
       <TextView
16
           android:id="@+id/id_label"
17
           android:layout_width="wrap_content"
18
           android:layout_height="wrap_content"
19
           android:text="@string/widget_id"
20
           style="@style/widget_text" />
21
           
22
       <TextView
23
           android:id="@+id/id_value"
24
           android:layout_width="wrap_content"
25
           android:layout_height="wrap_content"
26
           android:text="."
27
           style="@style/widget_text"  />
28
           
29
   </LinearLayout>
30
   
31
   <TextView
32
       android:id="@+id/launch_url"
33
       style="@style/widget_views"
34
       android:layout_width="wrap_content"
35
       android:layout_height="wrap_content"
36
       android:text="@string/URL"
37
       android:background="@drawable/tvbackground"/>
38
39
//Add a ‘Tap to update’ TextView//
40
41
   <TextView
42
       android:id="@+id/update"
43
       style="@style/widget_views"
44
       android:layout_width="wrap_content"
45
       android:layout_height="wrap_content"
46
       android:text="@string/update"
47
       android:background="@drawable/tvbackground"/>
48
49
   <LinearLayout
50
       android:background="@drawable/tvbackground"
51
       style="@style/widget_views"
52
       android:layout_width="match_parent"
53
       android:layout_height="wrap_content"
54
       android:orientation="vertical">
55
56
//Add a TextView that’ll display our ‘Last Update’ label//
57
58
   <TextView
59
       android:id="@+id/update_label"
60
       style="@style/widget_text"
61
       android:layout_width="match_parent"
62
       android:layout_height="wrap_content"
63
       android:text="@string/last_update"   />
64
65
//Add the TextView that’ll display the time of the last update// 
66
67
   <TextView
68
       android:id="@+id/update_value"
69
       style="@style/widget_text"
70
       android:layout_width="match_parent"
71
       android:layout_height="wrap_content"
72
       android:text="@string/time" />
73
   </LinearLayout>
74
75
</LinearLayout>

Tiếp theo, định nghĩa các string resource mới mà chúng ta tham chiếu trong layout:

1
<string name="update">Tap to update</string>
2
<string name="last_update">Last Update</string>
3
<string name="time">%1$s</string>

@string/time trông khác so với chuỗi thông thường của bạn, vì nó chỉ là một placeholder sẽ được thay thế khi ứng dụng chạy.

Điều này cho chúng ta layout hoàn chỉnh:

Preview the complete Android application widget layout Preview the complete Android application widget layout Preview the complete Android application widget layout

Cập nhật Widget của bạn dựa trên Lịch biểu

Vì đó là cách thức đơn giản nhất để thực hiện, tôi sẽ bắt đầu bằng cách tự động cập nhật widget của chúng ta, sau một khoảng thời gian nhất định đã trôi qua.

Khi tạo kiểu lịch biểu cập nhật tự động này, khoảng 1.800.000 mili giây (30 phút) là khoảng thời gian nhỏ nhất mà bạn có thể sử dụng. Ngay cả khi bạn thiết lập updatePeriodMillis của dự án xuống chưa đầy 30 phút, thì widget của bạn vẫn sẽ chỉ cập nhật mỗi nửa giờ một lần.

Quyết định tần suất cập nhật widget của bạn sẽ không bao giờ đơn giản. Cập nhật thường xuyên sẽ làm mau hết pin của thiết bị hơn, nhưng đặt cập nhật quá xa thì widget của bạn có thể hiển thị thông tin cũ đáng kể cho người dùng.

Để có được sự cân bằng phù hợp với dự án cụ thể của bạn, bạn sẽ cần phải test widget của mình thông qua nhiều tần số cập nhật và đo tác động của từng tần số đến thời lượng pin, đồng thời theo dõi xem nội dung của widget có bị lỗi thời hay không.

Vì thường xuyên - là - quá - thường xuyên là một trong những câu hỏi bực bội mà không có câu trả lời "đúng", nó có thể giúp có được một ý tưởng ​​thứ hai bằng cách thực hiện một số thử nghiệm người dùng. Bạn thậm chí có thể thiết lập một số thử nghiệm A/B để kiểm tra xem tần số cập nhật nào đó có tích cực hơn những tần số khác hay không.

Mở tập tin AppWidgetProviderInfo của dự án (res/xml/new_app_widget_info.xml) và bạn sẽ thấy rằng nó đã xác định khoảng thời gian cập nhật mặc định là 86.400.000 miligiây (24 giờ).

1
android:updatePeriodMillis="86400000"

Có rất nhiều widget chỉ cập nhật mỗi 24 giờ một lần; ví dụ: một widget hiển thị dự báo thời tiết chỉ cần lấy thông tin mới mỗi ngày một lần. Tuy nhiên, ngay cả khi widget của bạn chỉ cần cập nhật một lần mỗi ngày, thì đây không phải là lý tưởng khi thử nghiệm ứng dụng của bạn. Không ai muốn đợi đến 24 giờ để xem liệu phương thức onUpdate() của họ có hoạt động đúng hay không, do đó bạn thường sử dụng khoảng thời gian ngắn nhất có thể khi thực hiện thử nghiệm ban đầu và sau đó thay đổi giá trị này, nếu cần.

Để làm cho việc thử nghiệm ứng dụng của chúng ta trở nên dễ dàng hơn, tôi sẽ sử dụng khoảng thời gian nhỏ nhất có thể:

1
android:updatePeriodMillis="1800000"

Truy xuất và hiển thị thời gian hiện tại

Phương thức onUpdate() có trách nhiệm cập nhật các View của widget những thông tin mới. Nếu bạn mở widget provider của dự án (java/values​​/NewAppWidget.java) thì bạn sẽ thấy rằng nó đã chứa sườn cơ bản của phương thức onUpdate():

1
   @Override
2
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
3
4
       // There may be multiple widgets active, so update all of them//

5
6
       for (int appWidgetId : appWidgetIds) {
7
           updateAppWidget(context, appWidgetManager, appWidgetId);
8
       }
9
   }

Bất kể widget của bạn cập nhật tự động hay do tương tác của người dùng, thì tiến trình cập nhật hoàn toàn giống nhau: widget manager gửi broadcast một intent với hành động ACTION_APPWIDGET_UPDATE và lớp widget provider đáp ứng bằng cách gọi phương thức onUpdate() để lần lượt gọi phương thức trợ giúp updateAppWidget().

Một khi chúng ta đã cập nhật lớp NewAppWidget.java của chúng ta để truy xuất và hiển thị thời gian hiện tại, dự án của chúng ta sẽ sử dụng cùng một đoạn code này, bất kể phương thức onUpdate() được kích hoạt như thế nào:

1
import android.appwidget.AppWidgetManager;
2
import android.appwidget.AppWidgetProvider;
3
import android.content.Context;
4
import android.widget.RemoteViews;
5
import android.app.PendingIntent;
6
import android.content.Intent;
7
import android.net.Uri;
8
import java.text.DateFormat;
9
import java.util.Date;
10
11
public class NewAppWidget extends AppWidgetProvider {
12
13
   static void updateAppWidget(Context context,
14
                               AppWidgetManager appWidgetManager,
15
                               int appWidgetId) {
16
17
//Retrieve the time//

18
19
       String timeString =
20
               DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
21
22
 //Construct the RemoteViews object//

23
24
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
25
       views.setTextViewText(R.id.id_value, String.valueOf(appWidgetId));
26
27
//Retrieve and display the time// 

28
29
       views.setTextViewText(R.id.update_value,
30
               context.getResources().getString(
31
                       R.string.time, timeString));
32
33
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://code.tutsplus.com/"));
34
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
35
       views.setOnClickPendingIntent(R.id.launch_url, pendingIntent);
36
       appWidgetManager.updateAppWidget(appWidgetId, views);
37
   }
38
39
   @Override
40
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
41
42
 //If multiple widgets are active, then update them all//

43
44
       for (int appWidgetId : appWidgetIds) {
45
           updateAppWidget(context, appWidgetManager, appWidgetId);
46
       }
47
   }
48
}

Có một vấn đề với code ở trên: nếu người dùng chạm vào TextView nhiều lần trong khoảng thời gian một phút, sẽ không có dấu hiệu trực quan rằng widget đã được cập nhật, đơn giản vì không có thời gian mới để hiển thị. Điều này hoàn toàn không gây ra vấn đề cho người dùng cuối của bạn, không ai ngồi đó mà gõ vào TextView nhiều lần mỗi phút và tự hỏi tại sao thời gian không thay đổi. Tuy nhiên, đây có thể là một vấn đề khi thử nghiệm ứng dụng của bạn, vì nó có nghĩa là bạn sẽ phải chờ ít nhất 60 giây giữa hai lần kích hoạt phương thức onUpdate().

Để đảm bảo luôn có một tín hiệu xác nhận trực quan rằng phương thức onUpdate() đã chạy thành công, tôi sẽ hiển thị một toast bất cứ khi nào onUpdate() được gọi:

1
import android.widget.Toast;
2
...
3
...
4
...
5
6
@Override
7
8
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
9
   for (int appWidgetId : appWidgetIds) {
10
       updateAppWidget(context, appWidgetManager, appWidgetId);
11
       Toast.makeText(context, "Widget has been updated! ", Toast.LENGTH_SHORT).show();
12
13
  }
14
   }
15
}
Test that the widget is updating correctly by including a toastTest that the widget is updating correctly by including a toastTest that the widget is updating correctly by including a toast

Cập nhật Widget của bạn để dựa trên hành động của người dùng

Widget của chúng ta sẽ cập nhật tự động mỗi nửa giờ, nhưng còn về cập nhật để dựa trên sự tương tác của người dùng thì sao?

Chúng ta đã thiết lập được cái nền bằng cách thêm một TextView Tap to Update vào layout của chúng ta và mở rộng lớp NewAppWidget.java để truy xuất và hiển thị thời gian hiện tại bất cứ khi nào onUpdate() được gọi.

Điều duy nhất còn lại cần phải làm là tạo một Intent với AppWidgetManager.ACTION_APPWIDGET_UPDATE, đây là một hành động thông báo cho widget rằng đã đến lúc cập nhật, và sau đó gọi Intent này dựa theo người dùng tương tác với TextView Tap to Update.

Đây là lớp NewAppWidget.java hoàn chỉnh:

1
import android.appwidget.AppWidgetManager;
2
import android.appwidget.AppWidgetProvider;
3
import android.content.Context;
4
import android.widget.RemoteViews;
5
import android.app.PendingIntent;
6
import android.content.Intent;
7
import android.net.Uri;
8
import java.text.DateFormat;
9
import java.util.Date;
10
import android.widget.Toast;
11
12
public class NewAppWidget extends AppWidgetProvider {
13
   static void updateAppWidget(Context context,
14
                               AppWidgetManager appWidgetManager,
15
                               int appWidgetId) {
16
17
//Retrieve the current time//

18
19
       String timeString =
20
               DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
21
22
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
23
       views.setTextViewText(R.id.id_value, String.valueOf(appWidgetId));
24
       views.setTextViewText(R.id.update_value,
25
               context.getResources().getString(
26
                       R.string.time, timeString));
27
28
//Create an Intent with the AppWidgetManager.ACTION_APPWIDGET_UPDATE action//

29
30
       Intent intentUpdate = new Intent(context, NewAppWidget.class);
31
       intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
32
33
//Update the current widget instance only, by creating an array that contains the widget’s unique ID// 

34
35
       int[] idArray = new int[]{appWidgetId};
36
       intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray);
37
38
//Wrap the intent as a PendingIntent, using PendingIntent.getBroadcast()//

39
40
       PendingIntent pendingUpdate = PendingIntent.getBroadcast(
41
               context, appWidgetId, intentUpdate,
42
               PendingIntent.FLAG_UPDATE_CURRENT);
43
44
//Send the pending intent in response to the user tapping the ‘Update’ TextView//

45
46
       views.setOnClickPendingIntent(R.id.update, pendingUpdate);
47
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://code.tutsplus.com/"));
48
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
49
       views.setOnClickPendingIntent(R.id.launch_url, pendingIntent);
50
51
//Request that the AppWidgetManager updates the application widget//

52
53
       appWidgetManager.updateAppWidget(appWidgetId, views);
54
   }
55
56
   @Override
57
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
58
       for (int appWidgetId : appWidgetIds) {
59
           updateAppWidget(context, appWidgetManager, appWidgetId);
60
           Toast.makeText(context, "Widget has been updated! ", Toast.LENGTH_SHORT).show();
61
       }
62
   }
63
}

Việc có thể cập nhật một widget theo yêu cầu có thể là vô giá khi thử nghiệm dự án của bạn, vì nó có nghĩa là bạn sẽ không bao giờ phải đợi một khoảng thời gian cập nhật trôi qua. Ngay cả khi bạn không bao gồm chức năng này trong ứng dụng hoàn chỉnh, bạn có thể muốn thêm một nút Tap to Update tạm thời chỉ để làm cho công việc của bạn dễ dàng hơn khi kiểm tra dự án của bạn.

Tạo một hình ảnh xem trước

Widget của chúng ta giờ đây trông hoàn toàn khác với widget mà Android Studio tạo cho chúng ta một cách tự động, nhưng hiện tại nó vẫn đang sử dụng hình ảnh xem trước tự động được tạo ra.

Create a unique Android app widget preview imageCreate a unique Android app widget preview imageCreate a unique Android app widget preview image

Tốt nhất, là hình ảnh xem trước của bạn nên khuyến khích người dùng lựa chọn widget của bạn từ Widget Picker, nhưng ít nhất nó phải là một hình đại diện chính xác về cách thức mà widget của bạn thật sự trông như thế nào! Hiện tại, hình xem trước của chúng ta không có những hộp đánh dấu đó, vì vậy chúng ta cần tạo một hình mới.

Cách đơn giản nhất là sử dụng ứng dụng Widget Preview được bao gồm trong Android emulator.

Tạo một Widget Preview

Trước tiên, cài đặt dự án của bạn trên Android Virtual Device (AVD). Sau đó mở app drawer của AVD và khởi chạy ứng dụng Widget Preview. AVD sẽ hiển thị danh sách mọi ứng dụng đã được cài đặt trên thiết bị - lựa chọn ứng dụng của bạn từ danh sách.

Widget của bạn sẽ được hiển thị trên một background trống. Dành chút thời gian chỉnh lại kích thước và tinh chỉnh widget của bạn để nó trông ổn nhất, và một khi bạn hài lòng với kết quả, hãy chọn Take Snapshot. Ảnh chụp màn hình sẽ được lưu dưới dạng tập tin PNG trong thư mục Download của AVD. Để truy xuất tập tin này, hãy chuyển trở lại Android Studio và mở Device File Explorer bằng cách chọn View > Tool Windows > Device File Explorer từ thanh công cụ. Trong Device File Explorer, điều hướng đến thư mục sdcard/Download, nơi bạn sẽ tìm thấy hình ảnh xem trước được lưu dưới dạng [app_name] _ori_ [orientation].png.

Create a preview image using the Android Virtual Devices built-in Widget Preview appCreate a preview image using the Android Virtual Devices built-in Widget Preview appCreate a preview image using the Android Virtual Devices built-in Widget Preview app

Kéo hình này ra khỏi Android Studio và thả nó vào một nơi nào đó dễ truy xuất, chẳng hạn như desktop của bạn. Đặt tên cho hình ảnh có tính mô tả hơn, và sau đó kéo và thả hình ảnh vào thư mục drawable của dự án. Bây giờ bạn có thể mở tập tin AppWidgetProviderInfo (trong trường hợp này, đó là new_app_widget_info.xml) và thay đổi dòng code sau để tham chiếu đến hình ảnh xem trước mới của bạn:

1
 android:previewImage="@drawable/example_appwidget_preview"

Sau cùng, bạn có thể xoá example_appwidget_preview không cần thiết khỏi dự án của mình.

Kiểm thử Widget của bạn

Đã đến lúc thử nghiệm widget đã hoàn chỉnh của bạn!

Trước tiên, cài đặt dự án trên thiết bị Android của bạn hoặc AVD. Xoá bỏ tất cả các widget hiện có khỏi màn hình home của bạn, để bạn chắc rằng bạn đang làm việc với phiên bản mới nhất.

Để thêm widget của bạn, bấm vào bất kỳ không gian trống nào trên màn hình home, bấm vào Widget, và sau đó chọn widget của bạn. Bạn sẽ được phép thay đổi kích cỡ và đặt lại vị trí cho widget khi cần thiết.

Giá trị Last Update sẽ hiển thị thời gian bạn đã tạo widget này. Bấm vào Tap to Update và widget sẽ hiển thị một toast và một thời gian mới. Nếu bạn đặt thêm widget thứ hai trên màn hình home của bạn, nó sẽ hiển thị một thời gian khác só với cái đầu tiên.

Create multiple instances of the same application widgetCreate multiple instances of the same application widgetCreate multiple instances of the same application widget

Bạn có thể cập nhật một trong các widget này bằng cách chạm vào TextView Tap to Update. Nếu bạn kích hoạt cập nhật thủ công cho widget đầu tiên, thì widget thứ hai sẽ không được cập nhật và ngược lại.

Ngoài các cập nhật thủ công, App Widget Manager sẽ cập nhật tất cả các widget của bạn một lần mỗi 30 phút, dựa vào thời gian bạn tạo widget đầu tiên. Mặc dù cập nhật thủ công có thể dẫn đến các trường hợp hiển thị các thời điểm khác nhau, nhưng cứ mỗi nửa giờ một lần tất cả các widget sẽ được cập nhật đồng thời, tại thời điểm đó chúng sẽ hiển thị cùng một thời gian.

Bạn có thể muốn giữ một vài widget này trên màn hình home, do đó bạn có thể quan sát nội dung của chúng thay đổi như thế nào theo thời gian dựa theo sự kết hợp giữa cập nhật tự động và thủ công.

Tóm tắt

Trong loạt bài này, chúng ta đã tạo ra một widget cho ứng dụng Android minh hoạ cách cài đặt tất cả các tính năng phổ biến nhất được tìm thấy trong các widget ứng dụng. Nếu bạn đã theo dõi từ lúc bắt đầu, thì bạn sẽ tạo được một widget có thể cập nhật tự động và phản hồi với đầu vào của người dùng và có khả năng phản ứng với sự kiện onClick (bạn cũng có thể tải về dự án hoàn chỉnh từ GitHub ).

Trong bài tiếp theo, chúng ta sẽ xem xét một số phương pháp tốt nhất để bảo đảm rằng widget của bạn cung cấp một trải nghiệm người dùng tốt và cách nâng cấp widget của bạn với một Activity cấu hình.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.