转载请注明原创出处,谢谢!
- GitHub: @Ricco

效果图.png
AndroidManifest.xml
<receiver
android:name=".widget_card.LivePlanWidget"
android:exported="true"
android:label="直播计划">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.test.liveplanwidget.refresh" />
<action android:name="com.test.liveplanwidget.click.item" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider_info_live_plan" />
</receiver>
<service
android:name=".widget_card.LivePlanRemoteViewsService"
android:exported="true"
android:permission="android.permission.BIND_REMOTEVIEWS" />
- 注意android.permission.BIND_REMOTEVIEWS是必须要有的
res/xml/widget_provider_info_live_plan.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_widget_description_live_plan"
android:initialKeyguardLayout="@layout/widget_live_plan"
android:initialLayout="@layout/widget_live_plan"
android:minWidth="200dp"
android:minHeight="100dp"
android:previewImage="@drawable/icon_widget_live_plan_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen" />
public class LivePlanWidget extends AppWidgetProvider {
private static final String TAG = LivePlanWidget.class.toString() + "aaaaaaaa";
public static final String ACTION_REFRESH = "com.test.liveplanwidget.refresh";
public static final String ACTION_CLICK_ITEM = "com.test.liveplanwidget.click.item";
private CompositeDisposable mCompositeDisposable = null;
/**
* 调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新微件。
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.i(TAG, "onUpdate: " + Arrays.toString(appWidgetIds));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_live_plan);
}
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (ACTION_CLICK_ITEM.equals(intent.getAction())) {
// list item 点击事件
Intent startIntent = new Intent(context, MainActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startIntent.putExtra("from_widget", "live_plan_widget");
startIntent.putExtra("to_activity", "main");
startIntent.putExtra("widget_key", intent.getStringExtra("widget_key"));
context.startActivity(startIntent);
} else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(intent.getAction())) {
Log.i(TAG, "onReceive: 收到更新广播");
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
Log.i(TAG, "onReceive: getAction " + appWidgetId + " " + intent.getAction());
okhttp(context, AppWidgetManager.getInstance(context), appWidgetId);
} else {
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
Log.i(TAG, "onReceive: getAction " + appWidgetId + " " + intent.getAction());
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetId);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_live_plan);
} else {
// 收到0
Log.i(TAG, "onReceive: 收到appwidgetId为0的广播");
int[] appWidgetIds = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, LivePlanWidget.class));
Log.i(TAG, "onReceive 收到appwidgetId为0的广播->: " + Arrays.toString(appWidgetIds));
for (int id : appWidgetIds) {
updateAppWidget(context, AppWidgetManager.getInstance(context), id);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(id, R.id.list_live_plan);
}
}
}
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
if (mCompositeDisposable != null) {
mCompositeDisposable.clear();
}
mCompositeDisposable = null;
}
/**
* 更新显示
*/
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
try {
// 构造RemoteViews对象
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_live_plan);
int localUserId = PreferencesUtils.getInt(context, Constant.USERIDKEY);
if (localUserId == Constant.USERDEFAULTID) {
// 未登录
views.setViewVisibility(R.id.tv_title, View.GONE);
views.setViewVisibility(R.id.tv_count, View.GONE);
views.setViewVisibility(R.id.iv_arr, View.GONE);
views.setViewVisibility(R.id.list_live_plan, View.GONE);
views.setViewVisibility(R.id.ll_empty, View.GONE);
views.setViewVisibility(R.id.tv_login, View.VISIBLE);
} else {
// 登录
String data = PreferencesUtils.getString(context, SettingConstant.SP_KEY_LIVE_PLAN_WIDGET_DATA);
List<HomeLiveBean.EntityBean> list = null;
if (!TextUtils.isEmpty(data)) {
list = new Gson().fromJson(data, new TypeToken<List<HomeLiveBean.EntityBean>>() {
}.getType());
}
if (list == null || list.size() == 0) {
// 无数据
views.setViewVisibility(R.id.tv_title, View.VISIBLE);
views.setViewVisibility(R.id.tv_count, View.GONE);
views.setViewVisibility(R.id.iv_arr, View.GONE);
views.setViewVisibility(R.id.list_live_plan, View.GONE);
views.setViewVisibility(R.id.ll_empty, View.VISIBLE);
views.setViewVisibility(R.id.tv_login, View.GONE);
} else {
// 有数据
views.setViewVisibility(R.id.tv_title, View.VISIBLE);
views.setViewVisibility(R.id.tv_count, View.VISIBLE);
views.setViewVisibility(R.id.iv_arr, View.VISIBLE);
views.setViewVisibility(R.id.list_live_plan, View.VISIBLE);
views.setViewVisibility(R.id.ll_empty, View.GONE);
views.setViewVisibility(R.id.tv_login, View.GONE);
int curDayCount = 0;
String curStr = TimeUtils.date2Str(new Date(), "yyyy-MM-dd");
for (HomeLiveBean.EntityBean item : list) {
String lessonStartTime = item.getLessonStartTime();
if (curStr.equals(lessonStartTime.substring(0, 10))) {
curDayCount++;
}
}
if (curDayCount > 0) {
views.setTextViewText(R.id.tv_count, "今日共" + curDayCount + "场");
} else {
views.setTextViewText(R.id.tv_count, "全部共" + list.size() + "场");
}
// 设置列表数据
Intent intent = new Intent(context, LivePlanRemoteViewsService.class);
views.setRemoteAdapter(R.id.list_live_plan, intent);
// 设置点击事件
Intent gridIntent = new Intent(context, LivePlanWidget.class);
gridIntent.setAction(ACTION_CLICK_ITEM);
gridIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, gridIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 设置intent模板
views.setPendingIntentTemplate(R.id.list_live_plan, pendingIntent);
}
}
// 点击进入首页
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("from_widget", "live_plan_widget");
intent.putExtra("to_activity", "main");
PendingIntent pendingIntent;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_ONE_SHOT);
}
views.setOnClickPendingIntent(R.id.ll_root, pendingIntent);
// 更新小部件
appWidgetManager.updateAppWidget(appWidgetId, views);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 网络请求数据
*/
private void okhttp(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
try {
if (mCompositeDisposable == null) {
mCompositeDisposable = new CompositeDisposable();
}
String userId = String.valueOf(PreferencesUtils.getInt(context, Constant.USERIDKEY));
if (TextUtils.isEmpty(userId) || "0".equals(userId)) {
updateAppWidget(context, appWidgetManager, appWidgetId);
return;
}
TreeMap<String, String> paramsMap = ParameterUtils.getParamsMap();
mCompositeDisposable.add(
new HomeModel().getMyLiveList(Address.AUTHORIZATION_CODE, ParameterUtils.getSign(paramsMap), paramsMap.get(Constant.TIME_STAMP), userId)
.subscribe(homeLiveBean -> {
try {
if (homeLiveBean.isSuccess()) {
PreferencesUtils.putString(context, SettingConstant.SP_KEY_LIVE_PLAN_WIDGET_DATA, new Gson().toJson(homeLiveBean.getEntity()));
} else {
PreferencesUtils.putString(context, SettingConstant.SP_KEY_LIVE_PLAN_WIDGET_DATA, "");
}
// 更新小组件
updateAppWidget(context, appWidgetManager, appWidgetId);
} catch (Exception e) {
e.printStackTrace();
}
}, throwable -> throwable.printStackTrace()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 使用AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(id, R.id.list_live_plan);方法来更新列表数据
public class LivePlanRemoteViewsService extends RemoteViewsService {
private static final String TAG = LivePlanRemoteViewsService.class.toString() + "aaaaaaaa";
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
Log.i(TAG, "onGetViewFactory: ");
return new LivePlanRemoteViewsFactory(this, intent);
}
}
public class LivePlanRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private static final String TAG = LivePlanRemoteViewsFactory.class.toString() + "aaaaaaaaaaaaaaa";
private Context mContext;
private int mAppWidgetId;
private List<HomeLiveBean.EntityBean> list = new ArrayList<>();
// 加一层bitmap缓存,解决glide加载错乱的问题
private LruCacheUtils cache = new LruCacheUtils();
public LivePlanRemoteViewsFactory(Context context, Intent intent) {
this.mContext = context;
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
Log.i(TAG, "LivePlanRemoteViewsFactory: " + mAppWidgetId);
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
String data = PreferencesUtils.getString(mContext, SettingConstant.SP_KEY_LIVE_PLAN_WIDGET_DATA);
if (!TextUtils.isEmpty(data)) {
list = new Gson().fromJson(data, new TypeToken<List<HomeLiveBean.EntityBean>>() {
}.getType());
}
}
@Override
public void onDataSetChanged() {
Log.i(TAG, "onDataSetChanged: ");
String data = PreferencesUtils.getString(mContext, SettingConstant.SP_KEY_LIVE_PLAN_WIDGET_DATA);
if (!TextUtils.isEmpty(data)) {
list = new Gson().fromJson(data, new TypeToken<List<HomeLiveBean.EntityBean>>() {
}.getType());
}
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
}
@Override
public int getCount() {
return list.size();
}
@Override
public RemoteViews getViewAt(int position) {
Log.i(TAG, "getViewAt: " + position);
try {
HomeLiveBean.EntityBean item = list.get(position);
if (item != null) {
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.widget_item_live_plan);
Glide.with(mContext).asBitmap()
.load(R.drawable.bg_zhibojihua_app)
.transform(new CenterCrop(), new RoundedCorners(GeneralUtil.dip2px(mContext, 6)))
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
views.setImageViewBitmap(R.id.iv_img_bg, resource);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
if (cache == null) {
cache = new LruCacheUtils();
}
Bitmap bitmap = cache.get(item.getTeacher().getImageMap().getMobileUrlMap().getLarge());
if (bitmap != null) {
views.setImageViewBitmap(R.id.iv_img, bitmap);
} else {
Glide.with(mContext).asBitmap()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.apply(new RequestOptions().override(100, 100))
.load(item.getTeacher().getImageMap().getMobileUrlMap().getLarge())
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
Log.i(TAG, "getViewAt:获取图片成功 " + position + " " + item.getTeacher().getImageMap().getMobileUrlMap().getLarge());
cache.put(item.getTeacher().getImageMap().getMobileUrlMap().getLarge(), resource);
views.setImageViewBitmap(R.id.iv_img, resource);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
views.setTextViewText(R.id.tv_title, item.getCatalogName());
views.setTextViewText(R.id.tv_time,
TimeUtils.date2Str(TimeUtils.str2Date(item.getLessonStartTime(), "yyyy-MM-dd HH:mm:ss"), "MM月dd日 HH:mm")
+ "-" + TimeUtils.date2Str(TimeUtils.str2Date(item.getLessonEndTime(), "yyyy-MM-dd HH:mm:ss"), "HH:mm")
);
views.setViewVisibility(R.id.tv_span, item.getCourseType() == 2 ? View.VISIBLE : View.GONE);
views.setViewVisibility(R.id.tv_public_tag, item.getCourseType() == 2 ? View.VISIBLE : View.GONE);
// 点击事件
Intent fillInIntent = new Intent(mContext, LivePlanWidget.class);
fillInIntent.putExtra("widget_key", String.valueOf(item.getId()));
views.setOnClickFillInIntent(R.id.ll_root, fillInIntent);
return views;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@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;
}
}
- 使用glide加载网络图片,会有明显的错乱和不渲染的情况,所以用了一层LruCache缓存
list_live_plan.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/background"
android:layout_gravity="top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/icon_widget_live_plan_bg"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/dp_16">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="@dimen/dp_20"
android:layout_height="@dimen/dp_20"
android:src="@drawable/icon_widget_live_plan_logo" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_8"
android:layout_weight="1"
android:text="直播计划"
android:textColor="@color/gray_0"
android:textSize="@dimen/sp_18"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="今日共x场"
android:textColor="@color/gray_70"
android:textSize="@dimen/sp_12" />
<ImageView
android:id="@+id/iv_arr"
android:layout_width="@dimen/dp_18"
android:layout_height="@dimen/dp_18"
android:background="@drawable/icon_widget_live_plan_arr" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="@dimen/dp_110">
<ListView
android:id="@+id/list_live_plan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_6"
android:divider="@null"
android:listSelector="@color/transparent"
tools:listitem="@layout/widget_item_live_plan" />
<LinearLayout
android:id="@+id/ll_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="@dimen/dp_90"
android:layout_height="@dimen/dp_90"
android:src="@drawable/icon_empty_service_course" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂无直播计划"
android:textColor="@color/gray_70"
android:textSize="@dimen/sp_11" />
</LinearLayout>
<TextView
android:id="@+id/tv_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="登录查看直播计划"
android:textColor="@color/gray_50"
android:textSize="@dimen/sp_11"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
</LinearLayout>
</FrameLayout>
widget_item_live_plan.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_10"
android:gravity="center_vertical"
android:orientation="horizontal">
<FrameLayout
android:layout_width="@dimen/dp_58"
android:layout_height="@dimen/dp_40">
<ImageView
android:id="@+id/iv_img_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/iv_img"
android:layout_width="@dimen/dp_40"
android:layout_height="@dimen/dp_40"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:src="@drawable/default_place_square_img" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_8"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="论文高效写作法:写作技巧+案例模版"
android:textColor="@color/gray_20"
android:textSize="@dimen/sp_14" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_6"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="0x月0x日 17:00-21:00"
android:textColor="@color/gray_50"
android:textSize="@dimen/sp_11" />
<TextView
android:id="@+id/tv_span"
android:layout_width="@dimen/dp_1"
android:layout_height="@dimen/dp_6"
android:layout_marginStart="@dimen/dp_5"
android:layout_marginEnd="@dimen/dp_5"
android:background="@color/gray_90" />
<TextView
android:id="@+id/tv_public_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="公开课"
android:textColor="@color/green_n"
android:textSize="@dimen/sp_11" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
public class LruCacheUtils {
private LruCache<String, SoftReference<Bitmap>> cache = new LruCache<>(10);
public Bitmap get(String id) {
SoftReference<Bitmap> ref = cache.get(id);
if (ref != null) {
return ref.get();
}
return null;
}
public void put(String id, Bitmap bitmap) {
cache.put(id, new SoftReference<>(bitmap));
}
}