实现上图功能有两种思路。
一:普通做法,更新item的数据,不停调用notifydatachange ;
二:各管自家刷新。
一个下载对应一个下载线程。线程持有对应item在Listview中的位置。当该线程所对应的item可见时,获得该Item的progressbar更新。
第二种方式相对省资源效率更高。
一步步来解决关键问题:
1.进度条实现
不熟悉进度条progressbar的样式定义,可以翻系统的源码。
水平样式:
<pre name="code" class="java">
<style name="Widget.ProgressBar.Horizontal"> <item name="android:indeterminateOnly">false</item> <item name="android:progressDrawable">@android:drawable/progress_horizontal</item> <item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item> <item name="android:minHeight">20dip</item> <item name="android:maxHeight">20dip</item> <item name="android:mirrorForRtl">true</item> </style>
关键是:
<style name="Widget.ProgressBar.Horizontal">和android:indeterminateDrawable
制一个Item布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="86dp" android:paddingTop="15dp" tools:context="com.loopbanner.DiscoveryFragment"> <ImageView android:id="@+id/iv_soft_icon" android:layout_width="60dp" android:layout_height="60dp" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_marginStart="13dp" android:scaleType="fitXY" android:src="@mipmap/ic_launcher_round" /> <TextView android:layout_toLeftOf="@+id/rl_pro" android:id="@+id/tv_soft_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:layout_toRightOf="@+id/iv_soft_icon" android:maxLines="1" android:text="TextView" android:textColor="#333333" /> <TextView android:layout_toLeftOf="@+id/rl_pro" android:layout_toRightOf="@+id/iv_soft_icon" android:id="@+id/tv_introduce" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/tv_soft_name" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:maxLines="1" android:text="TextView" android:textColor="#999999" /> <RelativeLayout android:id="@+id/rl_pro" android:layout_width="60dp" android:layout_height="25dp" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:layout_marginEnd="12dp" android:gravity="center"> <ProgressBar android:id="@+id/progressbar" style="@android:style/Widget.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:indeterminateOnly="false" android:max="100" android:minHeight="25dp" android:progress="0" android:progressDrawable="@drawable/progress_bg_list" /> <TextView android:id="@+id/tv_donwload" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="下载" android:textColor="#F88C08" /> </RelativeLayout> <View android:id="@+id/view" android:layout_width="match_parent" android:layout_height="1px" android:layout_alignParentBottom="true" android:layout_marginLeft="83dp" android:background="#999999"> </View> </RelativeLayout>
自定义
android:indeterminateDrawable
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <shape> <corners android:radius="12.5dip" /> <solid android:color="#fff" /> <stroke android:width="1dp" android:color="#F88C08" /> </shape> </item> <item android:id="@android:id/secondaryProgress"> <clip> <shape> <corners android:radius="12.5dip" /> <gradient android:angle="270" android:centerColor="#EE5C42" android:centerY="0.75" android:endColor="#EE5C42" android:startColor="#EE5C42" /> </shape> </clip> </item> <item android:id="@android:id/progress"> <clip> <shape> <corners android:radius="12.5dip" /> <solid android:color="#FEE2CD" /> <stroke android:width="1dp" android:color="#F88C08" /> </shape> </clip> </item> </layer-list>
效果如下:
按一种思路,下载只针对data数据操作。adapter要不断刷新。这个原理比较简单,不再写了。
第二种思路的关键是,下载线程去刷新progressbar,关键点是找是当前是否可见的item并刷新。
看一下关键代码:
找到当前可见item是listView现有api,这里面出于设计模式考虑。activity不要跟下载线程有交互,减少耦合。那么数据作都与adapter去交互。所以使用以下方法获取ListView:
int firstItem = mAdapter.getListView().getFirstVisiblePosition();
int lastItem = mAdapter.getListView().getLastVisiblePosition();
下一步如何生成持有位置信息的下载类。通常方法都是要adapter 的getview方法里去设置tag.
vh.tvDonwload.setOnClickListener(onClickListener);
vh.tvDonwload.setTag(R.id.progressbar, vh.progressbar);
vh.tvDonwload.setTag(R.id.tag_progress_bar, vh.progressbar);
vh.tvDonwload.setTag(R.id.tag_positon, position);
vh.progressbar.setTag(R.id.tag_url);
如何设置多个tag请自行baidu,给一个唯一id和一下object ;
可以如下去做:
<resources> <string name="hello_blank_fragment">Hello blank fragment</string> <item name="tag_positon" type="id">1</item> <item name="tag_progress_bar" type="id">2</item> <item name="tag_url" type="id">3</item> </resources>
在点击时开启下载工作。
public View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v instanceof TextView) { int pos = (int) v.getTag(R.id.tag_positon); DiscoveryModel item = (DiscoveryModel) getItem(pos); if (item.getStatus() == DiscoveryModel.NORMAL) { ((TextView) v).setText("0%"); new FileDownLoaderTask(DiscoveryAdapter.this, pos); item.setStatus(DiscoveryModel.DOWNLOADING); } else if (item.getStatus() == DiscoveryModel.DOWNLOADING) { item.setStatus(DiscoveryModel.PAUSE); } } } };
最后是如何更新
看代码:
//如果本下载线程所带的item的位置在当前listview中可见 if (((DiscoveryModel) mAdapter.getItem(mPosition)).getStatus() == DiscoveryModel.DOWNLOADING && firstItem <= mPosition && mPosition <= lastItem) { //在listview中找到该item for (int i = 0; i < mAdapter.getListView().getChildCount(); i++) { //通过比对tag的位置 if ((int) (mAdapter.getListView().getChildAt(i).findViewById(R.id.tv_donwload).getTag(R.id.tag_positon)) == mPosition) { //找到ProgressBar 去更新; ProgressBar progressBar = (ProgressBar) mAdapter.getListView().getChildAt(i).findViewById(R.id.progressbar); progressBar.setProgress(process); TextView tv_donwload = (TextView) mAdapter.getListView().getChildAt(i) .findViewById(R.id.tv_donwload); progressBar.setProgress(process); tv_donwload.setText(process + "%"); } } }
帖上所有代码
MainActivity
package com.loopbanner; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; public class MainActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction ft = fragmentManager.beginTransaction(); ft.add(R.id.root, new DiscoveryFragment()); ft.commit(); } }
fragment
package com.loopbanner; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * A simple {@link Fragment} subclass. */ public class DiscoveryFragment extends Fragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_discovery, container, false); init(rootView); return rootView; } ListView mListView; DiscoveryAdapter mAdapter; List<Object> modelList; private void init(View rootView) { mListView = (ListView) rootView.findViewById(R.id.lv_discovery); modelList = new ArrayList<>(); mAdapter = new DiscoveryAdapter(getActivity(), modelList); mListView.setAdapter(mAdapter); getData(); } private void getData() { GetDiscoveryDataTask gddt = new GetDiscoveryDataTask() { @Override public void onResult(String resMsg, int code) { if (resMsg != null && resMsg.length() > 0) { parseData(resMsg); } else { Toast.makeText(getActivity(), "未获取到数据!", Toast.LENGTH_SHORT).show(); } } }; gddt.request(); } public void parseData(String string) { try { JSONObject jsonObject = new JSONObject(string); if (jsonObject != null) { JSONArray jsonArray = jsonObject.optJSONArray("apps"); if (jsonArray != null && jsonArray.length() > 0) { for (int i = 0; i < jsonArray.length(); i++) { DiscoveryModel dm = new DiscoveryModel(); JSONObject app = (JSONObject) jsonArray.get(i); dm.setSoftId(app.optString("softId")); dm.setSoftBrief(app.optString("softBrief")); dm.setSoftDown(app.optString("softDown")); dm.setSoftLogo(app.optString("softLogo")); dm.setSoftName(app.optString("softName")); modelList.add(dm); } mAdapter.notifyDataSetChanged(); } } } catch (JSONException e) { e.printStackTrace(); } } }
adpater
package com.loopbanner; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import com.bumptech.glide.Glide; import java.util.List; public class DiscoveryAdapter extends ArrayAdapter<Object> { private static class ViewHolder { public final RelativeLayout rootView; public final ImageView ivSoftIcon; public final TextView tvSoftName; public final TextView tvIntroduce; public final ProgressBar progressbar; public final TextView tvDonwload; public final View view; private ViewHolder(RelativeLayout rootView, ImageView ivSoftIcon, TextView tvSoftName, TextView tvIntroduce, ProgressBar progressbar, TextView tvDonwload, View view) { this.rootView = rootView; this.ivSoftIcon = ivSoftIcon; this.tvSoftName = tvSoftName; this.tvIntroduce = tvIntroduce; this.progressbar = progressbar; this.tvDonwload = tvDonwload; this.view = view; } public static ViewHolder create(RelativeLayout rootView) { ImageView ivSoftIcon = (ImageView) rootView.findViewById(R.id.iv_soft_icon); TextView tvSoftName = (TextView) rootView.findViewById(R.id.tv_soft_name); TextView tvIntroduce = (TextView) rootView.findViewById(R.id.tv_introduce); ProgressBar progressbar = (ProgressBar) rootView.findViewById(R.id.progressbar); TextView tvDonwload = (TextView) rootView.findViewById(R.id.tv_donwload); View view = (View) rootView.findViewById(R.id.view); return new ViewHolder(rootView, ivSoftIcon, tvSoftName, tvIntroduce, progressbar, tvDonwload, view); } } GlideRoundTransform glideRoundTransform; ListView mListView; public ListView getListView() { return mListView; } @Override public View getView(int position, View convertView, ViewGroup parent) { mListView = (ListView) parent; final ViewHolder vh; if (convertView == null) { View view = mInflater.inflate(R.layout.fragment_discovery_item, parent, false); vh = ViewHolder.create((RelativeLayout) view); view.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } DiscoveryModel item = (DiscoveryModel) getItem(position); vh.tvSoftName.setText(item.getSoftName()); vh.tvIntroduce.setText(item.getSoftBrief()); Glide.with(getContext()).load(item.getSoftLogo()).transform(this.glideRoundTransform).into(vh.ivSoftIcon); vh.progressbar.setProgress(item.getCompletePercent()); switch (item.getStatus()) { case DiscoveryModel.DOWNLOADING: vh.tvDonwload.setText(item.getCompletePercent() + "%"); break; case DiscoveryModel.NORMAL: vh.tvDonwload.setText("下载"); break; case DiscoveryModel.PAUSE: vh.tvDonwload.setText("暂停"); break; } vh.tvDonwload.setOnClickListener(onClickListener); vh.tvDonwload.setTag(R.id.progressbar, vh.progressbar); vh.tvDonwload.setTag(R.id.tag_progress_bar, vh.progressbar); vh.tvDonwload.setTag(R.id.tag_positon, position); vh.progressbar.setTag(R.id.tag_url); return vh.rootView; } private LayoutInflater mInflater; // Constructors public DiscoveryAdapter(Context context, List<Object> objects) { super(context, 0, objects); this.mInflater = LayoutInflater.from(context); glideRoundTransform = new GlideRoundTransform(getContext()); } public DiscoveryAdapter(Context context, Object[] objects) { super(context, 0, objects); this.mInflater = LayoutInflater.from(context); glideRoundTransform = new GlideRoundTransform(getContext()); } public View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (v instanceof TextView) { int pos = (int) v.getTag(R.id.tag_positon); DiscoveryModel item = (DiscoveryModel) getItem(pos); if (item.getStatus() == DiscoveryModel.NORMAL) { ((TextView) v).setText("0%"); new FileDownLoaderTask(DiscoveryAdapter.this, pos); item.setStatus(DiscoveryModel.DOWNLOADING); } else if (item.getStatus() == DiscoveryModel.DOWNLOADING) { item.setStatus(DiscoveryModel.PAUSE); } } } }; }
laoder模拟
package com.loopbanner; import android.os.Handler; import android.os.Message; import android.widget.ProgressBar; import android.widget.TextView; /** * 本类负责,下载数据,并更新UI * 更新逻辑为:当前类保存apapter引用。获取数据后,如果本类所属的item是可见的, * 则更新progressbar */ public class FileDownLoaderTask { private String mUrl; private int process = 0; private DiscoveryAdapter mAdapter; private int mPosition; public FileDownLoaderTask(DiscoveryAdapter adapter, int position) { mAdapter = adapter; mUrl = ((DiscoveryModel) adapter.getItem(position)).getSoftDown(); mHandler.sendEmptyMessageDelayed(1, 500); mPosition = position; } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); process += 1; ((DiscoveryModel) mAdapter.getItem(mPosition)).setCompletePercent(process); int firstItem = mAdapter.getListView().getFirstVisiblePosition(); int lastItem = mAdapter.getListView().getLastVisiblePosition(); if (((DiscoveryModel) mAdapter.getItem(mPosition)).getStatus() == DiscoveryModel.DOWNLOADING && firstItem <= mPosition && mPosition <= lastItem) { for (int i = 0; i < mAdapter.getListView().getChildCount(); i++) { if ((int) (mAdapter.getListView().getChildAt(i).findViewById(R.id.tv_donwload).getTag(R.id.tag_positon)) == mPosition) { ProgressBar progressBar = (ProgressBar) mAdapter.getListView().getChildAt(i) .findViewById(R.id.progressbar); progressBar.setProgress(process); TextView tv_donwload = (TextView) mAdapter.getListView().getChildAt(i) .findViewById(R.id.tv_donwload); progressBar.setProgress(process); tv_donwload.setText(process + "%"); } } } if (process != 100) { mHandler.sendEmptyMessageDelayed(1, 500); } } }; }
资源
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <shape> <corners android:radius="12.5dip" /> <solid android:color="#fff" /> <stroke android:width="1dp" android:color="#F88C08" /> </shape> </item> <item android:id="@android:id/secondaryProgress"> <clip> <shape> <corners android:radius="12.5dip" /> <gradient android:angle="270" android:centerColor="#EE5C42" android:centerY="0.75" android:endColor="#EE5C42" android:startColor="#EE5C42" /> </shape> </clip> </item> <item android:id="@android:id/progress"> <clip> <shape> <corners android:radius="12.5dip" /> <solid android:color="#FEE2CD" /> <stroke android:width="1dp" android:color="#F88C08" /> </shape> </clip> </item> </layer-list>