You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Widget中实现垂直步骤进度条的技术求助

Android Widget 垂直步骤进度条实现方案

核心适配思路(针对Widget内存限制)

Android App Widget的进程内存配额有限,不能直接使用常规View组件,必须基于RemoteViews实现。针对你的需求,重点注意两点:

  • 耗时操作(HTTP请求)必须放在后台执行,比如用WorkManager定期拉取数据,缓存到本地(推荐SharedPreferences)后再更新Widget。
  • 列表展示优先用系统原生的ListView(通过RemoteViews适配),不要引入第三方复杂控件,避免内存溢出。

垂直滑动实现

Widget中的ListView本身支持垂直滑动,只需在布局中设置合适的固定高度(比如200dp),用户即可滑动查看所有步骤。无需额外适配库,系统原生API就能满足需求。

数据处理优化

你提供的JSON处理代码存在逻辑错误(循环中从空的steps数组取数据),修正后的代码如下:

// 修正后的JSON数据处理逻辑
JSONArray steps = new JSONArray();
for(int i = 0; i < info.length(); i++){
    JSONObject infoItem = info.getJSONObject(i);
    Boolean reached = infoItem.getBoolean("reached");
    String message = infoItem.getString("message");
    String data = infoItem.getString("data");
    
    JSONObject item = new JSONObject();
    item.put("reached", reached);
    item.put("message", message);
    item.put("data", data);
    steps.put(item);
}

处理完成后,将steps转为JSON字符串缓存到SharedPreferences,供Widget的列表组件读取。

完整实现代码示例

1. RemoteViewsFactory(列表项渲染)

负责为每个步骤生成对应的RemoteViews,根据reached状态切换样式:

public class StepsRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private Context mContext;
    private JSONArray mSteps;

    public StepsRemoteViewsFactory(Context context, Intent intent) {
        mContext = context;
        // 从本地缓存读取步骤数据
        try {
            String stepsJson = PreferenceManager.getDefaultSharedPreferences(context)
                    .getString("steps_data", "[]");
            mSteps = new JSONArray(stepsJson);
        } catch (JSONException e) {
            e.printStackTrace();
            mSteps = new JSONArray();
        }
    }

    @Override
    public void onCreate() {}

    @Override
    public void onDataSetChanged() {
        // 数据更新时重新读取缓存
        try {
            String stepsJson = PreferenceManager.getDefaultSharedPreferences(mContext)
                    .getString("steps_data", "[]");
            mSteps = new JSONArray(stepsJson);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy() {}

    @Override
    public int getCount() {
        return mSteps.length();
    }

    @Override
    public RemoteViews getViewAt(int position) {
        RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_step_item);
        try {
            JSONObject step = mSteps.getJSONObject(position);
            boolean reached = step.getBoolean("reached");
            String message = step.getString("message");
            String data = step.getString("data");

            // 设置文本内容
            rv.setTextViewText(R.id.tv_message, message);
            rv.setTextViewText(R.id.tv_data, data);

            // 根据完成状态切换图标和文本颜色
            if (reached) {
                rv.setImageViewResource(R.id.iv_status, R.drawable.ic_step_completed);
                rv.setTextColor(R.id.tv_message, mContext.getColor(R.color.completed_text));
            } else {
                rv.setImageViewResource(R.id.iv_status, R.drawable.ic_step_pending);
                rv.setTextColor(R.id.tv_message, mContext.getColor(R.color.pending_text));
            }

            // 可选:为列表项设置点击事件
            Intent fillInIntent = new Intent();
            fillInIntent.putExtra("step_position", position);
            rv.setOnClickFillInIntent(R.id.step_item_layout, fillInIntent);

        } catch (JSONException 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;
    }
}

2. AppWidgetProvider(Widget核心逻辑)

绑定列表组件并更新Widget:

public class StepsWidgetProvider extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_steps);

        // 绑定ListView的RemoteViewsService
        Intent intent = new Intent(context, StepsRemoteViewsService.class);
        views.setRemoteAdapter(R.id.lv_steps, intent);

        // 设置空数据视图
        views.setEmptyView(R.id.lv_steps, R.id.tv_empty);

        // 更新Widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    // 提供RemoteViews的服务类
    public static class StepsRemoteViewsService extends RemoteViewsService {
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StepsRemoteViewsFactory(this.getApplicationContext(), intent);
        }
    }
}

3. 布局文件

  • Widget主布局(widget_steps.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <ListView
        android:id="@+id/lv_steps"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:divider="@null"
        android:dividerHeight="8dp"/>

    <TextView
        android:id="@+id/tv_empty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="暂无步骤数据"
        android:gravity="center"
        android:padding="16dp"/>
</LinearLayout>
  • 列表项布局(widget_step_item.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/step_item_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center_vertical"
    android:paddingVertical="8dp">

    <ImageView
        android:id="@+id/iv_status"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginEnd="12dp"/>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textStyle="bold"/>

        <TextView
            android:id="@+id/tv_data"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="@color/secondary_text"/>
    </LinearLayout>
</LinearLayout>

内存优化建议

  1. 所有HTTP请求和数据解析放在WorkManager中执行,完成后缓存数据再触发Widget更新,避免在Widget进程中做耗时操作。
  2. 列表项布局尽量扁平化,减少嵌套层级,降低RemoteViews的渲染开销。
  3. 状态图标使用矢量图(VectorDrawable),减少内存占用。
  4. 避免在RemoteViewsFactory中执行任何耗时逻辑,所有数据提前处理完成。

内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:52:06