金融行情列表,每 10 秒輪詢最新價格,需**僅刷新價格變動的 Item**,並對變動行執行**背景閃爍動畫**(紅漲綠跌)。
oldList 與新數據列表 newList。latestPri),記錄**變更的位置索引集合**。adapter.notifyItemChanged(position, payload) (RecyclerView) 或 ListView 自定義 updateView(position, newData) 僅刷新該行。getView() / onBindViewHolder() 中根據 payload 或標誌位啟動動畫。public class MarketList {
private int id;
private String name;
private double latestPri; // 最新價
private double markup; // 漲跌幅
// getter/setter/equals/hashCode 基於 id
}
public class MarketAdapter extends BaseAdapter {
private List<MarketList> data = new ArrayList<>();
private ListView listView;
// 僅更新變更行
public void updateChangedItems(List<MarketList> newData) {
for (int i = 0; i < data.size() && i < newData.size(); i++) {
MarketList old = data.get(i);
MarketList nw = newData.get(i);
if (Math.abs(old.getLatestPri() - nw.getLatestPri()) > 0.000001) {
// 價格變了
data.set(i, nw); // 更新數據源
// ListView 無 notifyItemChanged,需自定義刷新單行
updateSingleRow(listView, i, nw);
}
}
}
// 刷新指定位置的 View
private void updateSingleRow(ListView lv, int position, MarketList item) {
int visiblePos = position - lv.getFirstVisiblePosition();
if (visiblePos >= 0 && visiblePos < lv.getChildCount()) {
View view = lv.getChildAt(visiblePos);
ViewHolder holder = (ViewHolder) view.getTag();
bindViewHolder(holder, item, true); // true = 播放動畫
}
// 不可見行下次 getView 自然會綁定新數據
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_market, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
bindViewHolder(holder, data.get(position), false); // 初始綁定不動畫
return view;
}
private void bindViewHolder(ViewHolder holder, MarketList item, boolean animate) {
holder.tvName.setText(item.getName());
holder.tvPrice.setText(String.valueOf(item.getLatestPri()));
holder.tvChange.setText(String.valueOf(item.getMarkup()));
// 顏色
int color = item.getMarkup() >= 0 ? Color.RED : Color.GREEN;
holder.tvPrice.setTextColor(color);
holder.tvChange.setTextColor(color);
// 閃爍動畫
if (animate) {
AlphaAnimation anim = new AlphaAnimation(0.3f, 1f);
anim.setDuration(300);
anim.setInterpolator(new LinearInterpolator());
holder.itemView.startAnimation(anim);
}
}
static class ViewHolder {
TextView tvName, tvPrice, tvChange;
View itemView;
ViewHolder(View v) {
tvName = v.findViewById(R.id.id_mr_name);
tvPrice = v.findViewById(R.id.id_mr_new);
tvChange = v.findViewById(R.id.id_mr_range);
itemView = v;
}
}
}
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override public void run() {
new Handler(Looper.getMainLooper()).post(() ->
presenter.getMarketList(categoryId) // 網絡請求
);
}
}, 0, 10_000);
// Presenter 回調
@Override
public void onMarketListReceived(List<MarketList> newList) {
adapter.updateChangedItems(newList); // 僅刷新變更行
}
data.set(i, newItem) 保證 getView() 見到最新數據。getFirstVisiblePosition() + getChildAt() 直接操作 View,避免 notifyDataSetChanged() 全量重繪導致閃屏、滾動位置重置。getView() 自然讀取已更新的 data。AlphaAnimation、ObjectAnimator、ValueAnimator 均可,注意 startAnimation() 會重置動畫狀態。新項目**強烈建議用 RecyclerView** + ListAdapter + DiffUtil.ItemCallback,框架自動計算差異、調用 notifyItemChanged(payload)、支持預取動畫,代碼量減半、性能更強。
掌握「數據源單一來源 + 差異計算 + 局部刷新 + 載荷動畫」模式,ListView/RecyclerView 皆通用。