Android Jamendo开源在线音乐播放器源码分析九 ViewFlipper及自定义布局控件的分析

本文详细介绍了如何利用ViewFlipper空间实现界面的动态切换,并通过自定义布局和控件如ProgressBar、FailureBar、RemoteImageView等优化加载体验和错误处理,特别关注于专辑加载过程中的界面展示逻辑。

在之前分析过这个ViewFlipper代码:

备注一:ViewFlipper

界面的最上面的mViewFlipper中包含了mGallery、mProgressBar、mFailureBar,刚开始以为最上面部分的显示是通过不同状态下设置View的visibility 的VISIBLEINVISIBLE, or GONE.来显示的,看完代码之后才知道是用的ViewFlipper这个空间实现的,代码显得更加清楚。

  1. <com.teleca.jamendo.util.FixedViewFlipper  
  2.         android:orientation="vertical" android:id="@+id/ViewFlipper"  
  3.         android:layout_width="fill_parent" android:layout_height="75dip"  
  4.         android:background="@drawable/gradient_dark_purple">  
  5.   
  6.         <!-- (0) Loading -->  
  7.         <LinearLayout android:orientation="vertical"  
  8.             android:layout_width="fill_parent" android:layout_height="fill_parent"  
  9.             android:layout_marginLeft="15dip" android:gravity="left|center_vertical">  
  10.             <com.teleca.jamendo.widget.ProgressBar  
  11.                 android:id="@+id/ProgressBar" android:layout_width="wrap_content"  
  12.                 android:layout_height="wrap_content">  
  13.             </com.teleca.jamendo.widget.ProgressBar>  
  14.         </LinearLayout>  
  15.   
  16.         <!-- (1) Gallery -->  
  17.         <LinearLayout android:orientation="vertical"  
  18.             android:layout_width="fill_parent" android:layout_height="fill_parent"  
  19.             android:gravity="center">  
  20.             <Gallery android:id="@+id/Gallery" android:layout_width="fill_parent"  
  21.                 android:layout_height="wrap_content" android:spacing="0px" />  
  22.         </LinearLayout>  
  23.   
  24.         <!-- (2) Failure -->  
  25.         <LinearLayout android:orientation="vertical"  
  26.             android:layout_width="fill_parent" android:layout_height="fill_parent"  
  27.             android:layout_marginLeft="15dip" android:gravity="left|center_vertical">  
  28.             <com.teleca.jamendo.widget.FailureBar  
  29.                 android:id="@+id/FailureBar" android:layout_width="wrap_content"  
  30.                 android:layout_height="wrap_content">  
  31.             </com.teleca.jamendo.widget.FailureBar>  
  32.         </LinearLayout>  
  33.     </com.teleca.jamendo.util.FixedViewFlipper>  
在onCreate()里面启动一个AsyncTask来加载,根据不同的结果决定显示ViewFlipper的哪部分内容

  1. private class NewsTask extends AsyncTask<Void, WSError, Album[]> {  
  2.   
  3.         @Override  
  4.         public void onPreExecute() {  
  5.             mViewFlipper.setDisplayedChild(0);  
  6.             mProgressBar.setText(R.string.loading_news);  
  7.             super.onPreExecute();  
  8.         }  
  9.   
  10.         @Override  
  11.         public Album[] doInBackground(Void... params) {  
  12.             JamendoGet2Api server = new JamendoGet2ApiImpl();  
  13.             Album[] albums = null;  
  14.             try {  
  15.                 albums = server.getPopularAlbumsWeek();  
  16.             } catch (JSONException e) {  
  17.                 e.printStackTrace();  
  18.             } catch (WSError e){  
  19.                 publishProgress(e);  
  20.             }  
  21.             return albums;  
  22.         }  
  23.   
  24.         @Override  
  25.         public void onPostExecute(Album[] albums) {  
  26.   
  27.             if(albums != null && albums.length > 0){  
  28.                 mViewFlipper.setDisplayedChild(1);  
  29.                 ImageAdapter albumsAdapter = new ImageAdapter(HomeActivity.this);  
  30.                 albumsAdapter.setList(albums);  
  31.                 mGallery.setAdapter(albumsAdapter);  
  32.                 mGallery.setOnItemClickListener(mGalleryListener);  
  33.                 mGallery.setSelection(albums.length/2true); // animate to center  
  34.   
  35.             } else {  
  36.                 mViewFlipper.setDisplayedChild(2);  
  37.                 mFailureBar.setOnRetryListener(new OnClickListener(){  
  38.   
  39.                     @Override  
  40.                     public void onClick(View v) {  
  41.                         new NewsTask().execute((Void)null);  
  42.                     }  
  43.   
  44.                 });  
  45.                 mFailureBar.setText(R.string.connection_fail);  
  46.             }  
  47.             super.onPostExecute(albums);  
  48.         }  
  49.   
  50.         @Override  
  51.         protected void onProgressUpdate(WSError... values) {  
  52.             Toast.makeText(HomeActivity.this, values[0].getMessage(), Toast.LENGTH_LONG).show();  
  53.             super.onProgressUpdate(values);  
  54.         }  
  55.           
  56.           
  57.   
  58.     }  
在这个AsyncTask中进行专辑的加载,在加载时onPreExecute()中mViewFlipper.setDisplayedChild(0);也就是上面xml代码中的(0) Loading部分,然后在doInBackground()中进行专辑的加载,当加载完之后onPostExecute(Album[] albums),然后根据加载专辑是否成功,选择相应的界面,是显示加载成功之后的(1) Gallery还是加载失败之后的(2) Failure。其实这里关键是这个空间ViewFlipper,在API中的解释是:

android.widget.ViewFlipper

Simple ViewAnimator that will animate between two or more views that have been added to it. Only one child is shown at a time. If requested, can automatically flip between each child at a regular interval.

备注二:自定义布局、控件

在com.teleca.jamendo.widget这个包中都是一些自定义的布局或者组件,这些布局和组件都是可以复用的。

ProgressBar

<!-- (0) Loading -->
<LinearLayout android:orientation="vertical"
	android:layout_width="fill_parent" android:layout_height="fill_parent"
	android:layout_marginLeft="15dip" android:gravity="left|center_vertical">
	<com.teleca.jamendo.widget.ProgressBar
		android:id="@+id/ProgressBar" android:layout_width="wrap_content"
		android:layout_height="wrap_content">
	</com.teleca.jamendo.widget.ProgressBar>
</LinearLayout>			

这个是当程序加载的时候显示的

/**
 * Widget notifying user of ongoing action 
 * 
 * @author Lukasz Wisniewski
 */
public class ProgressBar extends LinearLayout {
	
	protected TextView mTextView;
	
	public ProgressBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public ProgressBar(Context context) {
		super(context);
		init();
	}
	
	/**
	 * Sharable code between constructors
	 */
	private void init(){
		LayoutInflater.from(getContext()).inflate(R.layout.progress_bar, this);
		
		mTextView = (TextView)findViewById(R.id.ProgressTextView);
	}
	
	/**
	 * Sets informative text
	 * 
	 * @param resid
	 */
	public void setText(int resid){
		mTextView.setText(resid);
	}

}

progress_bar.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">
	<LinearLayout android:layout_height="wrap_content"
		android:minHeight="75dip" android:layout_width="fill_parent"
		android:orientation="horizontal" android:gravity="center_vertical"
		android:paddingLeft="13dip" android:paddingRight="13dip">

		<ProgressBar android:id="@+id/ProgressBar"
			android:layout_width="48dip" android:layout_height="48dip"></ProgressBar>

		<LinearLayout android:layout_height="wrap_content"
			android:paddingLeft="13dip" android:orientation="vertical"
			android:layout_width="fill_parent">
			<TextView android:id="@+id/ProgressTextView"
				android:layout_width="wrap_content" android:layout_height="wrap_content"
				android:text="@string/loading" android:textSize="20dip" android:textColor="#ffffff"></TextView>
		</LinearLayout>

	</LinearLayout>
</merge>

我使用的时候只要mProgressBar.setText(R.string.loading_news);就可以设置他显示的文字信息

RemoteImageView

/**
 * ImageView extended class allowing easy downloading
 * of remote images
 * 
 * @author Lukasz Wisniewski
 */
public class RemoteImageView extends ImageView{
	
	/**
	 * Maximum number of unsuccesful tries of downloading an image
	 */
	private static int MAX_FAILURES = 3;

	public RemoteImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	public RemoteImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public RemoteImageView(Context context) {
		super(context);
		init();
	}

	/**
	 * Sharable code between constructors
	 */
	private void init(){
	}
	
	/**
	 * Remote image location
	 */
	private String mUrl;
	
	/**
	 * Currently successfully grabbed url
	 */
	private String mCurrentlyGrabbedUrl;
	
	/**
	 * Remote image download failure counter
	 */
	private int mFailure;

	/**
	 * Position of the image in the mListView
	 */
	private int mPosition;

	/**
	 * ListView containg this image
	 */
	private ListView mListView;
	
	/**
	 * Default image shown while loading or on url not found
	 */
	private Integer mDefaultImage;

	/**
	 * Loads image from remote location
	 * 
	 * @param url eg. http://random.com/abz.jpg
	 */
	public void setImageUrl(String url){
		
		if(mListView == null && mCurrentlyGrabbedUrl != null && mCurrentlyGrabbedUrl.equals(url)){
			// do nothing image is grabbed & loaded, we are golden
			return;
		}
		
		if(mUrl != null && mUrl.equals(url)){
			mFailure++;
			if(mFailure > MAX_FAILURES){
				Log.e(JamendoApplication.TAG, "Failed to download "+url+", falling back to default image");
				loadDefaultImage();
				return;
			}
		} else {
			mUrl = url;
			mFailure = 0;
		}

		ImageCache imageCache = JamendoApplication.getInstance().getImageCache();
		if(imageCache.isCached(url)){
			this.setImageBitmap(imageCache.get(url));
		}
		else {
			try{
				new DownloadTask().execute(url);
			} catch (RejectedExecutionException e) {
				// do nothing, just don't crash
			}
		}
	}
	
	/**
	 * Sets default local image shown when remote one is unavailable
	 * 
	 * @param resid
	 */
	public void setDefaultImage(Integer resid){
		mDefaultImage = resid;
	}
	
	/**
	 * Loads default image
	 */
	private void loadDefaultImage(){
		if(mDefaultImage != null)
			setImageResource(mDefaultImage);
	}
	
	/**
	 * Loads image from remote location in the ListView
	 * 
	 * @param url eg. http://random.com/abz.jpg
	 * @param position ListView position where the image is nested
	 * @param listView ListView to which this image belongs
	 */
	public void setImageUrl(String url, int position, ListView listView){
		mPosition = position;
		mListView = listView;
		setImageUrl(url);
	}

	/**
	 * Asynchronous image download task
	 * 
	 * @author Lukasz Wisniewski
	 */
	class DownloadTask extends AsyncTask<String, Void, String>{
		
		private String mTaskUrl;

		@Override
		public void onPreExecute() {
			loadDefaultImage();
			super.onPreExecute();
		}

		@Override
		public String doInBackground(String... params) {

			mTaskUrl = params[0];
			InputStream stream = null;
			URL imageUrl;
			Bitmap bmp = null;

			try {
				imageUrl = new URL(mTaskUrl);
				try {
					stream = imageUrl.openStream();
					bmp = BitmapFactory.decodeStream(stream);
					try {
						if(bmp != null){
							JamendoApplication.getInstance().getImageCache().put(mTaskUrl, bmp);
							Log.d(JamendoApplication.TAG, "Image cached "+mTaskUrl);
						} else {
							Log.w(JamendoApplication.TAG, "Failed to cache "+mTaskUrl);
						}
					} catch (NullPointerException e) {
						Log.w(JamendoApplication.TAG, "Failed to cache "+mTaskUrl);
					}
				} catch (IOException e) {
					Log.w(JamendoApplication.TAG, "Couldn't load bitmap from url: " + mTaskUrl);
				} finally {
					try {
						if(stream != null){
							stream.close();
						}
					} catch (IOException e) {}
				}

			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
			return mTaskUrl;
		}

		@Override
		public void onPostExecute(String url) {
			super.onPostExecute(url);
			
			// target url may change while loading
			if(!mTaskUrl.equals(mUrl))
				return;
			
			Bitmap bmp = JamendoApplication.getInstance().getImageCache().get(url);
			if(bmp == null){
				Log.w(JamendoApplication.TAG, "Trying again to download " + url);
				RemoteImageView.this.setImageUrl(url);
			} else {
				
				// if image belongs to a list update it only if it's visible
				if(mListView != null)
					if(mPosition < mListView.getFirstVisiblePosition() || mPosition > mListView.getLastVisiblePosition())
						return;
				
				RemoteImageView.this.setImageBitmap(bmp);
				mCurrentlyGrabbedUrl = url;
			}
		}

	};

}
正如作者所说ImageView extended class allowing easy downloading of remote images。这个很好的获取图片的封装类,使我想显示图片的时候不用关心怎么从网络获取图片以及怎么生成图片,我只要将图片的URL地址传进来就可以了,获取完图片调用父类ImageView的setImageBitmap就可以显示想要的图片了。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值