AppWidget là gì
AppWidget, hay còn gọi là widget trên màn hình chính, là các ứng dụng nhỏ có thể hiển thị trực tiếp trên màn hình chính của hệ điều hành Android. Hãy xem hình ảnh minh họa:
Trong hình, các thành phần được đánh dấu bằng mũi tên vàng là AppWidget. Một số ứng dụng được người dùng sử dụng thường xuyên có thể được biến thành AppWidget để tiện lợi hơn. Các ví dụ điển hình bao gồm đồng hồ, thời tiết, trình phát nhạc, v.v. AppWidget là một phần của ứng dụng Android, có mục đích đặc biệt. Nếu được sử dụng đúng cách, nó thực sự có thể làm cho ứng dụng của bạn trở nên hấp dẫn hơn. Nguyên lý hoạt động của nó là nhúng các điều khiển của một tiến trình vào cửa sổ của một tiến trình khác. Khi nhấn và giữ vào một khoảng trống trên màn hình chính, một thư mục AppWidget sẽ xuất hiện. Trong đó, bạn có thể tìm thấy AppWidget tương ứng, nhấn và giữ để kéo nó ra và thêm vào màn hình chính.
Cách phát triển AppWidget
AppWidget được điều khiển thông qua BroadCastReceiver. Lớp chính để phát triển AppWidget là AppWidgetProvider, lớp này kế thừa từ BroadCastReceiver. Để triển khai widget trên màn hình, nhà phát triển chỉ cần phát triển một lớp con kế thừa từ AppWidgetProvider và ghi đè phương thức onUpdate() của nó. Việc ghi đè phương thức này thường bao gồm các bước sau:
1. Tạo một đối tượng RemoteViews, đối tượng này tải tệp bố cục giao diện của widget.
2. Thiết lập các thuộc tính của các phần tử trong tệp bố cục được RemoteViews tải.
3. Tạo một đối tượng ComponentName
4. Gọi AppWidgetManager để cập nhật widget.
Hãy xem một ví dụ thực tế, sử dụng ví dụ được tạo tự động bởi Android Studio. (Lưu ý: Tôi đang sử dụng phiên bản AS mới nhất là 2.2.3, được gọi tắt là AS.)
Tạo một dự án HelloWorld mới, sau đó tạo một AppWidget, đặt tên là CustomWidgetProvider, nhấn下一步 theo mặc định để hoàn thành phát triển AppWidget đơn giản nhất. Sau khi chạy chương trình, thêm widget vào màn hình chính. Các bước thao tác và kết quả mặc định như sau:
Hãy xem AS đã tạo ra những mã nguồn nào cho chúng ta? So sánh với các bước đã nêu ở trên, chúng ta hãy xem xét.
Đầu tiên, có một lớp CustomWidgetProvider.
package com.example.vietnamese.widgetdemo;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
/**
* Triển khai chức năng của Widget ứng dụng.
*/
public class CustomWidgetProvider extends AppWidgetProvider {
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
CharSequence widgetText = context.getString(R.string.widget_text);
// Xây dựng đối tượng RemoteViews
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.custom_widget_provider);
views.setTextViewText(R.id.widget_text, widgetText);
// Yêu cầu trình quản lý widget cập nhật widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// Có thể có nhiều widget đang hoạt động, vì vậy hãy cập nhật tất cả chúng
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onEnabled(Context context) {
// Thực hiện các chức năng liên quan khi widget đầu tiên được tạo
}
@Override
public void onDisabled(Context context) {
// Thực hiện các chức năng liên quan khi widget cuối cùng bị vô hiệu hóa
}
}
Lớp này kế thừa từ AppWidgetProvider, AS đã tự động ghi đè phương thức onUpdate() cho chúng ta, duyệt qua appWidgetIds và gọi phương thức updateAppWidget(). Hãy xem phương thức updateAppWidget(), rất đơn giản, chỉ có bốn dòng:
Dòng đầu tiên, CharSequence widgetText = context.getString(R.string.widget_text);
Khai báo một chuỗi;
Dòng thứ hai, RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.custom_widget_provider);
Tạo một đối tượng RemoteViews, tham số đầu tiên truyền tên gói ứng dụng, tham số thứ hai chỉ định tệp bố cục mà RemoteViews tải. Dòng này tương ứng với điểm đầu tiên được đề cập ở trên. Có thể thấy trong thư mục res/layout/, AS đã tự động tạo một tệp my_app_widget_provider.xml, nội dung như sau:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#09C"
android:padding="@dimen/widget_margin">
<TextView
android:id="@+id/widget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_margin="8dp"
android:background="#09C"
android:contentDescription="@string/widget_text"
android:text="@string/widget_text"
android:textColor="#ffffff"
android:textStyle="bold|italic" />
</RelativeLayout>
Tệp này chính là giao diện của widget trên màn hình chính mà chúng ta thấy. Tệp bố cục chỉ chứa một TextView. Lúc này bạn có thể hỏi, có thể thêm hình ảnh không? Có, chỉ cần thêm ImageView như trong bố cục Activity bình thường là được. Người thông minh có thể bắt đầu suy nghĩ về việc tùy chỉnh kiểu dáng của widget, thêm các điều khiển tùy chỉnh mạnh mẽ, hấp dẫn và đẹp mắt, rất tiếc, không được. Các thành phần có thể được thêm vào tệp bố cục widget là có hạn chế. Nội dung chi tiết sẽ được giới thiệu khi nói về RemoteViews sau này.
Dòng thứ ba, views.setTextViewText(R.id.widget_text, widgetText);
Gán chuỗi được khai báo ở dòng đầu tiên cho TextView trong tệp bố cục ở trên, lưu ý rằng khi gán giá trị, hãy chỉ định id của TextView, phải khớp với nhau. Dòng này tương ứng với điểm thứ hai được đề cập ở trên.
Dòng thứ tư, appWidgetManager.updateAppWidget(appWidgetId, views);
Ở đây gọi phương thức appWidgetManager.updateAppWidget() để cập nhật widget. Dòng này tương ứng với điểm thứ tư được đề cập ở trên.
Lúc này, bạn có thể có thắc mắc, ở trên rõ ràng đã nói có bốn bước, trong đó bước thứ ba, tạo một đối tượng ComponentName, rõ ràng là không cần thiết. Thật vậy, ví dụ này cũng không sử dụng. Nếu chúng ta tự gõ mã cho bước thứ tư, gợi ý thông minh của AS sẽ cho bạn biết rằng appWidgetManager.updateAppWidget() có ba phương thức nạp chồng. Trong mã nguồn, ba phương thức này không được viết cùng nhau, để tiện lợi, tôi sao chép và dán phần giới thiệu từ tài liệu API chính thức
void |
updateAppWidget(ComponentName provider, RemoteViews views) Đặt RemoteViews để sử dụng cho tất cả các phiên bản AppWidget cho nhà cung cấp AppWidget đã cung cấp. |
|---|---|
void |
updateAppWidget(int\[\] appWidgetIds, RemoteViews views) Đặt RemoteViews để sử dụng cho các appWidgetIds được chỉ định. |
void |
updateAppWidget(int appWidgetId, RemoteViews views) Đặt RemoteViews để sử dụng cho appWidgetId được chỉ định. |
Ba phương thức này đều nhận hai tham số, tham số thứ hai là đối tượng RemoteViews. Trong đó, tham số đầu tiên của phương thức đầu tiên là đối tượng ComponentName, cập nhật tất cả các phiên bản AppWidget do AppWidgetProvider cung cấp, phương thức thứ hai cập nhật tập hợp các đối tượng AppWidget được chỉ định rõ ràng, phương thức thứ ba, cập nhật một đối tượng AppWidget được chỉ định rõ ràng. Vì vậy, chúng ta thường sử dụng phương thức đầu tiên, áp dụng cho tất cả các đối tượng AppWidget, chúng ta cũng có thể chọn cập nhật theo nhu cầu.
Đến đây, tất cả các bước đã kết thúc, xong chưa? Chưa. Trước đây đã nói, CustomWidgetProvider tùy chỉnh kế thừa từ AppWidgetProvider, và AppWidgetProvider lại kế thừa từ BroadCastReceiver,
Vì vậy, CustomWidgetProvider về bản chất là một bộ thu phát sóng, là một trong bốn thành phần chính, cần được đăng ký trong tệp kê khai của chúng ta. Mở tệp AndroidManifest.xml, bạn có thể thấy rằng nó đã đăng ký widget, nội dung như sau:
<receiver android:name=".CustomWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/custom_widget_provider_info" />
</receiver>
Ở trên có một Action, Action này phải được thêm và không thể thay đổi, thuộc về quy chuẩn hệ thống, là để nhận dạng widget. Nếu không thêm, bộ thu này sẽ không xuất hiện trong danh sách widget. Sau đó thấy widget chỉ định @xml/custom_widget_provider_info làm meta-data, bạn cẩn thận sẽ phát hiện ra rằng trong thư mục res/, một thư mục xml đã được tạo, bên dưới tạo một tệp my_app_widget_provider_info.xml, nội dung như sau:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/custom_widget_provider"
android:initialLayout="@layout/custom_widget_provider"
android:minHeight="40dp"
android:minWidth="40dp"
android:previewImage="@drawable/example_widget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen">
</appwidget-provider>
Ở đây đã cấu hình một số thông tin cơ bản của widget, các thuộc tính thường dùng có initialLayout là bố cục khởi tạo của widget, minHeight định nghĩa chiều cao tối thiểu của widget, previewImage chỉ định hình ảnh xem trước của widget trong danh sách widget, updatePeriodMillis chỉ định chu kỳ cập nhật của widget, đơn vị là mili giây. Các thuộc tính khác, bạn có thể xem tài liệu API.
Đến đây, quá trình phát triển widget đơn giản nhất ở trên thực sự đã kết thúc. Để phát triển widget mạnh mẽ hơn, chúng ta cần tìm hiểu thêm về RemoteViews và AppWidgetProvider.
Trang phục của AppWidget——RemoteViews
Dưới đây là một số lớp liên quan đến RemoteViews.
1.1 RemoteViews
RemoteViews, theo nghĩa đen, có thể hiểu là nó là một view từ xa. Nó là một view từ xa, hiển thị trong một tiến trình khác, nhưng có thể được cập nhật trong một tiến trình khác. Các trường hợp sử dụng chính của RemoteViews trong Android bao gồm: thông báo tùy chỉnh và widget trên màn hình chính.
Trong hàm tạo của RemoteViews, tham số thứ hai nhận một tệp layout để xác định view của RemoteViews; sau đó, chúng ta gọi các phương thức set trong RemoteViews để thiết lập các thuộc tính của các thành phần trong layout, ví dụ, có thể gọi setTextViewText() để thiết lập văn bản của thành phần TextView.
Trước đây đã đề cập, các thành phần có thể được thêm vào tệp bố cục widget là có hạn chế, các loại View mà nó hỗ trợ bao gồm bốn loại bố cục: FrameLayout, LinearLayout, RelativeLayout, GridLayout và 13 loại View: AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar, TextView, ViewFlipper, ListView, GridView, StackView, AdapterViewFlipper, ViewSub. Lưu ý: RemoteViews cũng không hỗ trợ các lớp con của các View trên.
RemoteViews cung cấp một loạt các phương thức setXXX() để thiết lập thuộc tính cho các view con của widget. Bạn có thể tham khảo tài liệu API để biết chi tiết.
1.2 RemoteViewsService
RemoteViewsService, là dịch vụ quản lý RemoteViews. Thông thường, khi AppWidget chứa các view tập hợp như GridView, ListView, StackView, thì mới cần sử dụng RemoteViewsService để cập nhật, quản lý. Các bước chung để RemoteViewsService cập nhật view tập hợp là:
(01) Thiết lập RemoteViews tương ứng với RemoteViewsService bằng phương thức setRemoteAdapter().
(02) Sau đó trong RemoteViewsService, triển khai giao diện RemoteViewsFactory. Sau đó, trong giao diện RemoteViewsFactory, thiết lập các mục trong view tập hợp, ví dụ như mỗi mục trong ListView.
1.3 RemoteViewsFactory
Qua phần giới thiệu về RemoteViewsService, chúng ta biết rằng RemoteViewsService quản lý layout thông qua RemoteViewsFactory, RemoteViewsFactory là một giao diện nội bộ trong RemoteViewsService. RemoteViewsFactory cung cấp một loạt các phương thức để quản lý từng mục trong view tập hợp. Ví dụ:
RemoteViews getViewAt(int position)
Lấy view của mục thứ position trong "view tập hợp" bằng getViewAt(), view được trả về dưới dạng đối tượng RemoteViews.
int getCount()
Lấy tổng số các mục con trong "view tập hợp" bằng getCount().
Sắc đẹp của AppWidget——AppWidgetProvider
Chúng ta nói một nữ đồng nghiệp xinh đẹp, ngoài việc cô ấy mặc quần áo, trang điểm đẹp thì, tôi nghĩ nguyên nhân chính nhất vẫn là chính cô ấy xinh đẹp. Tương tự, widget có khả năng gắn vào màn hình chính, cập nhật view giữa các tiến trình chủ yếu là vì AppWidgetProvider là một bộ thu phát sóng. Chúng ta thấy rằng trong ví dụ trên, mã nguồn được AS tự động tạo ra, ngoài phương thức onUpdate() được chúng ta ghi đè, còn có ghi đè onEnable() và onDisable() hai phương thức, nhưng đều là các phương thức rỗng, hai phương thức này được gọi khi nào? Và, chúng ta nói CustomWidgetProvider tùy chỉnh, kế thừa từ AppWidgetProvider, và CustomWidgetProvider lại là lớp con của BroadCastReceiver, nhưng chúng ta lại không ghi đè phương thức onReceiver() như bộ thu phát sóng thông thường? Hãy đi sâu vào mã nguồn của AppWidgetProvider để khám phá.
Lớp này không có nhiều mã, thực ra, ngoài phương thức khởi tạo, AppWidgetProvider chỉ có các phương thức sau:
onEnable() : Khi widget được thêm vào màn hình chính lần đầu tiên, phương thức này được gọi lại, có thể thêm nhiều lần, nhưng chỉ gọi lần đầu tiên. Tương ứng với Action của broadcast là ACTION_APPWIDGET_ENABLE.
onUpdate(): Khi widget được thêm vào hoặc mỗi lần widget được cập nhật, phương thức này sẽ được gọi một lần. Chu kỳ cập nhật của widget được cấu hình trong tệp cấu hình updatePeriodMillis, mỗi lần cập nhật sẽ gọi. Tương ứng với Action của broadcast là: ACTION_APPWIDGET_UPDATE và ACTION_APPWIDGET_RESTORED.
onDisabled(): Khi widget cuối cùng của loại này bị xóa khỏi màn hình chính, phương thức này được gọi, tương ứng với Action của broadcast là ACTION_APPWIDGET_DISABLED.
onDeleted(): Mỗi khi xóa một widget, phương thức này sẽ được gọi. Tương ứng với Action của broadcast là ACTION_APPWIDGET_DELETED.
onRestored(): Khi widget được khôi phục từ bản sao lưu, hoặc khi khôi phục cài đặt, phương thức này sẽ được gọi, ít được sử dụng trong thực tế. Tương ứng với Action của broadcast là ACTION_APPWIDGET_RESTORED.
onAppWidgetOptionsChanged(): Khi bố cục của widget thay đổi, phương thức này sẽ được gọi. Tương ứng với Action của broadcast là ACTION_APPWIDGET_OPTIONS_CHANGED.
Cuối cùng là phương thức onReceive(), AppWidgetProvider đã ghi đè phương thức này, dùng để phân phối các sự kiện cụ thể cho các phương thức trên. Hãy xem mã nguồn:
public void onReceive(Context context, Intent intent) {
// Bảo vệ chống lại các broadcast cập nhật không chính thống (không thực sự là vấn đề bảo mật,
// chỉ cần lọc các broadcast xấu để các lớp con ít có khả năng bị lỗi).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds != null && oldIds.length > 0) {
this.onRestored(context, oldIds, newIds);
this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
}
}
}
}
Thực hành AppWidget
Hãy tự viết một ví dụ khác để học các kiến thức khác của RemoteViews, ví dụ này sử dụng button và listview trong bố cục widget. Đưa mã lên:
Tệp bố cục của widget complex_widget_provider.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="100dp"
android:layout_height="200dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_test"
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher"/>
<Button
android:id="@+id/btn_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Nhấp để chuyển"/>
</LinearLayout>
<TextView
android:layout_width="1dp"
android:layout_height="200dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#f00"/>
<ListView
android:id="@+id/lv_test"
android:layout_width="100dp"
android:layout_height="200dp">
</ListView>
</LinearLayout>
Thông tin cấu hình của widget complex_widget_provider_info.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/complex_widget_provider"
android:minHeight="200dp"
android:minWidth="200dp"
android:previewImage="@mipmap/a1"
android:updatePeriodMillis="86400000">
</appwidget-provider>
ComplexWidgetProvider.java:
package com.example.vietnamese.widgetdemo;
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.os.Bundle;
import android.text.TextUtils;
import android.widget.RemoteViews;
import android.widget.Toast;
public class ComplexWidgetProvider extends AppWidgetProvider {
public static final String CHANGE_IMAGE = "com.example.vietnamese.action.CHANGE_IMAGE";
private RemoteViews mRemoteViews;
private ComponentName mComponentName;
private int[] imgs = new int[]{
R.mipmap.a1,
R.mipmap.b2,
R.mipmap.c3,
R.mipmap.d4,
R.mipmap.e5,
R.mipmap.f6
};
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.complex_widget_provider);
mRemoteViews.setImageViewResource(R.id.iv_test, R.mipmap.ic_launcher);
mRemoteViews.setTextViewText(R.id.btn_test, "Nhấp để chuyển đến Activity");
Intent skipIntent = new Intent(context, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT);
mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);
// Thiết lập adapter cho ListView.
// (01) intent: intent tương ứng để khởi chạy ListViewService(RemoteViewsService)
// (02) setRemoteAdapter: thiết lập adapter cho ListView
// Liên kết ListView và ListViewService thông qua setRemoteAdapter,
// để đạt được mục đích cập nhật gridview thông qua GridWidgetService.
Intent lvIntent = new Intent(context, ListViewService.class);
mRemoteViews.setRemoteAdapter(R.id.lv_test, lvIntent);
mRemoteViews.setEmptyView(R.id.lv_test,android.R.id.empty);
// Thiết lập intent mẫu cho ListView
// Giải thích: "Các điều khiển tập hợp (như GridView, ListView, StackView, v.v.)" chứa nhiều phần tử con, ví dụ: GridView chứa nhiều ô.
// Chúng không thể đặt sự kiện nhấp như các nút thông thường bằng setOnClickPendingIntent, phải thực hiện hai bước.
// (01) Đặt "mẫu intent" bằng setPendingIntentTemplate, đây là không thể thiếu!
// (02) Sau đó trong giao diện getViewAt() của lớp RemoteViewsFactory xử lý "các điều khiển tập hợp", đặt "dữ liệu của một mục trong điều khiển tập hợp"
/*
* setPendingIntentTemplate đặt mẫu pendingIntent
* setOnClickFillInIntent có thể thêm fillInIntent vào pendingIntent
*/
Intent toIntent = new Intent(CHANGE_IMAGE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 200, toIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mRemoteViews.setPendingIntentTemplate(R.id.lv_test, pendingIntent);
mComponentName = new ComponentName(context, ComplexWidgetProvider.class);
appWidgetManager.updateAppWidget(mComponentName, mRemoteViews);
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if(TextUtils.equals(CHANGE_IMAGE,intent.getAction())){
Bundle extras = intent.getExtras();
int position = extras.getInt(ListViewService.INITENT_DATA);
mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.complex_widget_provider);
mRemoteViews.setImageViewResource(R.id.iv_test, imgs[position]);
mComponentName = new ComponentName(context, ComplexWidgetProvider.class);
AppWidgetManager.getInstance(context).updateAppWidget(mComponentName, mRemoteViews);
}
}
}
MainActivity.java:
package com.example.vietnamese.widgetdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Phần quan trọng nhất là cách sử dụng ListView trong widget:
package com.example.vietnamese.widgetdemo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import java.util.ArrayList;
import java.util.List;
public class ListViewService extends RemoteViewsService {
public static final String INITENT_DATA = "extra_data";
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new ListRemoteViewsFactory(this.getApplicationContext(), intent);
}
private class ListRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private Context mContext;
private List<String> mList = new ArrayList<>();
public ListRemoteViewsFactory(Context context, Intent intent) {
mContext = context;
}
@Override
public void onCreate() {
mList.add("Một");
mList.add("Hai");
mList.add("Ba");
mList.add("Bốn");
mList.add("Năm");
mList.add("Sáu");
}
@Override
public void onDataSetChanged() {
}
@Override
public void onDestroy() {
mList.clear();
}
@Override
public int getCount() {
return mList.size();
}
@Override
public RemoteViews getViewAt(int position) {
RemoteViews views = new RemoteViews(mContext.getPackageName(), android.R.layout.simple_list_item_1);
views.setTextViewText(android.R.id.text1, "item:" + mList.get(position));
Bundle extras = new Bundle();
extras.putInt(ListViewService.INITENT_DATA, position);
Intent changeIntent = new Intent();
changeIntent.setAction(ComplexWidgetProvider.CHANGE_IMAGE);
changeIntent.putExtras(extras);
/* android.R.layout.simple_list_item_1 --- id --- text1
* listview's item click: gửi changeIntent,
* changeIntent mặc định có action là action được đặt trong provider bằng setPendingIntentTemplate */
views.setOnClickFillInIntent(android.R.id.text1, changeIntent);
return views;
}
/* Khi cập nhật giao diện nếu tốn thời gian sẽ hiển thị "Đang tải..." mặc định, nhưng bạn có thể thay đổi giao diện này
* Nếu trả về null, hiển thị giao diện mặc định
* Ngược lại, tải giao diện tùy chỉnh, trả về 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;
}
}
}
Cuối cùng, hãy xem tệp kê khai:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.vietnamese.widgetdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<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=".ComplexWidgetProvider"
android:label="@string/app_name">
<intent-filter>
<action android:name="com.example.vietnamese.action.CHANGE_IMAGE"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/complex_widget_provider_info">
</meta-data>
</receiver>
<service android:name=".ListViewService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false"
android:enabled="true"/>
</application>
</manifest>
Widget này được thêm vào màn hình chính có một ImageView hiển thị robot nhỏ, bên dưới có một Button, bên phải có một ListView.
Ở đây, hãy xem cách sử dụng Button và ListView trong RemoteViews.
Việc đặt Text cho Button giống như TextView vì Button kế thừa từ TextView. Cách đặt sự kiện nhấp cho Button như sau:
Intent skipIntent = new Intent(context, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT);
mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);
Sử dụng phương thức setOnClickPendingIntent, PendingIntent biểu thị Intent bị trì hoãn, cách sử dụng giống như trong thông báo. Khi nhấp vào, nó sẽ chuyển đến MainActivity.
Việc sử dụng ListView phức tạp hơn một chút. Đầu tiên, cần phải tự định nghĩa một lớp kế thừa từ RemoteViewsServices và ghi đè phương thức onGetViewFactory, trả về đối tượng của giao diện RemoteViewsService.RemoteViewsFactory. Ở đây, một lớp nội bộ được định nghĩa để triển khai giao diện này, cần phải ghi đè nhiều phương thức, tương tự như việc sử dụng ListView với nhiều bố cục. Phương thức quan trọng là
public RemoteViews getViewAt(int position){}
Trong phương thức này, chỉ định bố cục và nội dung của mỗi mục trong ListView, đồng thời đặt sự kiện nhấp cho mục bằng setOnClickFillInIntent() hoặc setOnClickPendingIntent(). Ở đây, tôi thực hiện việc nhấp vào mục, thay thế hình ảnh của ImageView bên trái. Ghi đè phương thức onReceiver của lớp ComplexWidgetProvider để xử lý logic thay thế hình ảnh.
Kết quả chạy chương trình như hình sau: