本讲涉及的类:

HomeActivity

RequestCache

JamendoGet2ApiImpl

JamendoGet2Api

WSError

AlbumClickedListener

PurpleListener

PurpleEntity

Caller

 

Home页面UI如下图所示:

(一) TitleBar的实现:自定义ViewFilpper

自定义的ViewFlipper中,包含三个自定义组件:

Xml文件显示如下:

  1. <com.teleca.jamendo.util.FixedViewFlipper 
  2.         android:id="@+id/ViewFlipper" 
  3.         android:layout_width="fill_parent" 
  4.         android:layout_height="75dip" 
  5.         android:background="@drawable/gradient_dark_purple" 
  6.         android:orientation="vertical" > 
  7.  
  8.         <!-- (0) Loading --> 
  9.  
  10.         <LinearLayout 
  11.             android:layout_width="fill_parent" 
  12.             android:layout_height="fill_parent" 
  13.             android:layout_marginLeft="15dip" 
  14.             android:gravity="left|center_vertical" 
  15.             android:orientation="vertical" > 
  16.  
  17.             <com.teleca.jamendo.widget.ProgressBar 
  18.                 android:id="@+id/ProgressBar" 
  19.                 android:layout_width="wrap_content" 
  20.                 android:layout_height="wrap_content" > 
  21.             </com.teleca.jamendo.widget.ProgressBar> 
  22.         </LinearLayout> 
  23.  
  24.         <!-- (1) Gallery --> 
  25.  
  26.         <LinearLayout 
  27.             android:layout_width="fill_parent" 
  28.             android:layout_height="fill_parent" 
  29.             android:gravity="center" 
  30.             android:orientation="vertical" > 
  31.  
  32.             <Gallery 
  33.                 android:id="@+id/Gallery" 
  34.                 android:layout_width="fill_parent" 
  35.                 android:layout_height="wrap_content" 
  36.                 android:spacing="0px" /> 
  37.         </LinearLayout> 
  38.  
  39.         <!-- (2) Failure --> 
  40.  
  41.         <LinearLayout 
  42.             android:layout_width="fill_parent" 
  43.             android:layout_height="fill_parent" 
  44.             android:layout_marginLeft="15dip" 
  45.             android:gravity="left|center_vertical" 
  46.             android:orientation="vertical" > 
  47.  
  48.             <com.teleca.jamendo.widget.FailureBar 
  49.                 android:id="@+id/FailureBar" 
  50.                 android:layout_width="wrap_content" 
  51.                 android:layout_height="wrap_content" > 
  52.             </com.teleca.jamendo.widget.FailureBar> 
  53.         </LinearLayout> 
  54.     </com.teleca.jamendo.util.FixedViewFlipper> 

1,自定义ProgressBar

   如下图所示:

2,自定义FailureBar

   如下图所示:

简化起见,只列出FailureBar的代码:

  1. public class FailureBar extends LinearLayout{ 
  2.      
  3.     protected TextView mTextView; 
  4.     protected Button mRetryButton; 
  5.      
  6.     public FailureBar(Context context, AttributeSet attrs) { 
  7.         super(context, attrs); 
  8.         init(); 
  9.     } 
  10.  
  11.     public FailureBar(Context context) { 
  12.         super(context); 
  13.         init(); 
  14.     } 
  15.      
  16.     /** 
  17.      * Sharable code between constructors 
  18.      */ 
  19.     private void init(){ 
  20.         LayoutInflater.from(getContext()).inflate(R.layout.failure_bar, this); 
  21.          
  22.         mTextView = (TextView)findViewById(R.id.ProgressTextView); 
  23.         mRetryButton = (Button)findViewById(R.id.RetryButton); 
  24.     } 
  25.      
  26.     /** 
  27.      * Sets informative text 
  28.      *  
  29.      * @param resid 
  30.      */ 
  31.     public void setText(int resid){ 
  32.         mTextView.setText(resid); 
  33.     } 
  34.      
  35.     /** 
  36.      * Sets action to be performed when Retry button is clicked  
  37.      *  
  38.      * @param l 
  39.      */ 
  40.     public void setOnRetryListener(OnClickListener l){ 
  41.         mRetryButton.setOnClickListener(l); 
  42.     } 
  43.  

关键代码在init()方法中,即组合式自定义布局,很简单。按键的监听命名很好。适合我们的习惯 。

 

(二) 自定义Listener接口

1,AlbumClickedListener

   我靠,这个接口在HomeActivity里根本没有用到,虽然实现了这个接口。这个接口看了好一会,觉得比较怪,打了log,Album点击事件其实是放在Gallery的点击事件里完成的。代码如下:

  1. private OnItemClickListener mGalleryListener = new OnItemClickListener(){ 
  2.  
  3.         @Override 
  4.         public void onItemClick(AdapterView<?> adapterView, View view, int position, 
  5.                 long id) { 
  6.             Album album = (Album) adapterView.getItemAtPosition(position); 
  7.             PlayerActivity.launch(HomeActivity.this, album); 
  8.             Log.d("album_click""done in GalleryListener"); 
  9.         } 
  10.          
  11.     }; 

2,PurpleListener:监听主ListView每一栏点击事件

代码如下:

  1. public interface PurpleListener { 
  2.      
  3.     /** 
  4.      * Callback to be invoked when PurpleEntry is selected 
  5.      */ 
  6.     public void performAction(); 

 主ListView内容的封装类PurpleEntry中的构造方法,其中一个参数是PurpleListener,代码如下:

  1. public PurpleEntry(Integer drawable, Integer text, PurpleListener listener) { 
  2.         mDrawable = drawable; 
  3.         mTextId = text; 
  4.         mListener = listener; 
  5.     } 

 主Activity中,PurpleEntity通过以下的方式实现了PurpleListener,代码如下:

  1. browseListEntry.add(new PurpleEntry(R.drawable.list_search, R.string.search, new PurpleListener(){ 
  2.             @Override 
  3.             public void performAction() { 
  4.                 SearchActivity.launch(HomeActivity.this); 
  5.             } 
  6.         })); 

(三) 手势

Xml布局如下:

  1. <android.gesture.GestureOverlayView 
  2.         xmlns:android="http://schemas.android.com/apk/res/android" 
  3.         android:id="@+id/gestures" 
  4.         android:layout_width="fill_parent" 
  5.         android:layout_height="fill_parent" 
  6.         android:eventsInterceptionEnabled="false" 
  7.         android:gestureStrokeType="multiple" 
  8.         android:orientation="vertical" > 
  9.  
  10.         <ListView 
  11.             android:id="@+id/HomeListView" 
  12.             android:layout_width="fill_parent" 
  13.             android:layout_height="fill_parent" 
  14.             android:divider="#000" /> 
  15.     </android.gesture.GestureOverlayView> 

代码如下:

首先:onResume()方法中获取手势的设置:是否使用手势,代码如下:

  1. @Override 
  2.     protected void onResume() { 
  3.         fillHomeListView(); 
  4.         boolean gesturesEnabled = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("gestures"true); 
  5.         mGestureOverlayView.setEnabled(gesturesEnabled); 
  6.         super.onResume(); 
  7.     } 

再在onCreate()方法中调用相关方法,Jamendo中在JamendoApp中做了封装,本文暂不涉及其封装,会在未来分析,只列出HomeActivity中的代码:

  1. mGestureOverlayView = (GestureOverlayView) findViewById(R.id.gestures); 
  2.         mGestureOverlayView.addOnGesturePerformedListener(JamendoApplication 
  3.                 .getInstance().getPlayerGestureHandler()); 

 

如果要单独实现手势,具体的实现步骤很简单,我不在此赘述,只列出顺序:

1, 创建gesture lib

2, 导入项目

    如下图所示:

3, 代码引入并实现

(四) 封装:以TitleBar为例展开

在本例中,图片的加载使用异步AsyncTask完成。代码如下:

AsyncTask的方法进行的顺序依次为,参看下图,单击放大:

绿色部分在主线程,黑色部分在其他线程。

1, 错误提示:

WSError继承Throwable封装了错误信息。通过publishProgress()方法,刷新UI错误提示部分。在通过http请求的过程中,捕获到相关异常,初始化一个WSError对象,抛出,即可在关联的TextView上显示出来。

WSError代码如下:

  1. public class WSError extends Throwable { 
  2.  
  3.     /** 
  4.      *  
  5.      */ 
  6.     private static final long serialVersionUID = 1L; 
  7.      
  8.     private String message; 
  9.  
  10.     public WSError() { 
  11.     } 
  12.      
  13.     public WSError(String message) { 
  14.         this.message = message; 
  15.     } 
  16.      
  17.     public void setMessage(String message) { 
  18.         this.message = message; 
  19.     } 
  20.  
  21.     public String getMessage() { 
  22.         return message; 
  23.     } 
  24.  

当捕获到相关异常时,抛出WSError即可,如下示例代码(Caller类中执行HttpGet连接):

  1. try { 
  2.             // execute request 
  3.             try { 
  4.                 httpResponse = httpClient.execute(httpGet);//apache http step3: 
  5.             } catch (UnknownHostException e) { 
  6.                 throw new WSError("Unable to access " + e.getLocalizedMessage()); 
  7.             } catch (SocketException e){ 
  8.                 throw new WSError(e.getLocalizedMessage()); 
  9.             } 

 

2, 数据获取:

A, JamendoGet2ApiImpl实现接口JamendoGet2Api定义的方法。

我注意到,JamendoGet2Api类的作者为:Lukasz Wisniewski,JamendoGet2ApiImpl类的作者为 Lukasz Wisniewski,Marcin Gil。显然为了提高效率,有人定义了方法,有人去实现。在这里,我的体会是接口体现架构的意味。

B, JamendoGet2ApiImpl内部定义了Caller类,封装了ApacheHttp的api。在此,涉及到缓存的问题。因为资源的使用是全局的,所以在入口处JamendoApplication类中初始化了缓存类RequestCache,此处缓存的是最近的十次请求内容

缓存类代码如下:

  1. public class RequestCache { 
  2.      
  3.     // TODO cache lifeTime 
  4.      
  5.     private static int CACHE_LIMIT = 10
  6.      
  7.     @SuppressWarnings("unchecked"
  8.     private LinkedList history; 
  9.     private Hashtable<String, String> cache; 
  10.      
  11.     @SuppressWarnings("unchecked"
  12.     public RequestCache(){ 
  13.         history = new LinkedList(); 
  14.         cache = new Hashtable<String, String>(); 
  15.     } 
  16.      
  17.      
  18.     @SuppressWarnings("unchecked"
  19.     public void put(String url, String data){ 
  20.         history.add(url); 
  21.         // too much in the cache, we need to clear something 
  22.         if(history.size() > CACHE_LIMIT){ 
  23.             String old_url = (String) history.poll(); 
  24.             cache.remove(old_url); 
  25.         } 
  26.         cache.put(url, data); 
  27.     } 
  28.      
  29.     public String get(String url){ 
  30.         return cache.get(url); 
  31.     } 

显然,Jamendo使用的是HashTable,为什么不使用HashMap,两者的不同,见http://mikewang.blog.51cto.com/3826268/856865,不在此赘述!

缓存的实现:

使用LinkedList存储url,并定义最大缓存数为10,当LinkedList的size大于10,就使用poll()方法,获取并移除此列表的头(第一个元素)。

C, Android总使用Apache的http请求主要有以下五个步骤:参考代码的注释:

Step1:创建一个HttpClient(或获取现有应用)

Step2:实例化新Http方法(三类,get,post,put)

Step3: 设置Http参数名称值,例如绑定Entity或设置延时

Step4:使用HttpClient执行Http调用

Step5:处理Http响应

代码如下,标记出了顺序,第一步第二步先后没有关系:

  1. public static String doGet(String url) throws WSError{ 
  2.          
  3.         String data = null
  4.         if(requestCache != null){ 
  5.             data = requestCache.get(url); 
  6.             if(data != null){ 
  7.                 Log.d(JamendoApplication.TAG, "Caller.doGet [cached] "+url); 
  8.                 return data; 
  9.             } 
  10.         } 
  11.          
  12.         URI encodedUri = null
  13.         HttpGet httpGet = null
  14.          
  15.         try { 
  16.             encodedUri = new URI(url); 
  17.             httpGet = new HttpGet(encodedUri);//apache http step2: 
  18.              
  19.         } catch (URISyntaxException e1) { 
  20.             // at least try to remove spaces 
  21.             String encodedUrl = url.replace(' ''+'); 
  22.             httpGet = new HttpGet(encodedUrl); 
  23.                      
  24.             e1.printStackTrace(); 
  25.         } 
  26.          
  27.         // initialize HTTP GET request objects 
  28.         HttpClient httpClient = new DefaultHttpClient();//apache http step1: 
  29.         HttpResponse httpResponse; 
  30.          
  31.          
  32.         try { 
  33.             // execute request 
  34.             try { 
  35.                 httpResponse = httpClient.execute(httpGet);//apache http step3: 
  36.             } catch (UnknownHostException e) { 
  37.                 throw new WSError("Unable to access " + e.getLocalizedMessage()); 
  38.             } catch (SocketException e){ 
  39.                 throw new WSError(e.getLocalizedMessage()); 
  40.             } 
  41.             //apche http step4: 
  42.              
  43.             // request data 
  44.             HttpEntity httpEntity = httpResponse.getEntity();//apache http step5: 
  45.              
  46.             if(httpEntity != null){ 
  47.                 InputStream inputStream = httpEntity.getContent(); 
  48.                 data = convertStreamToString(inputStream); 
  49.                 // cache the result 
  50.                 if(requestCache != null){ 
  51.                     requestCache.put(url, data); 
  52.                 } 
  53.             } 
  54.              
  55.         } catch (ClientProtocolException e) { 
  56.             e.printStackTrace(); 
  57.         } catch (IOException e) { 
  58.             e.printStackTrace(); 
  59.         } 
  60.          
  61.         Log.d(JamendoApplication.TAG, "Caller.doGet "+url); 
  62.         return data; 
  63.     } 

D, 其他

缓存也可以使用线程池来完成,以后我会整理相关内容。对于缓存的处理更加简单。

3, 主UI

效果如下图:

A, 在onResume()方法中加载主ListView和Gesture,代码如下:

  1. @Override 
  2.     protected void onResume() { 
  3.         fillHomeListView(); 
  4.         boolean gesturesEnabled = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("gestures"true); 
  5.         mGestureOverlayView.setEnabled(gesturesEnabled); 
  6.         super.onResume(); 
  7.     } 

   在onCreate()方法中,执行标题栏的异步加载(AsyncTask),代码如下:

  1. new NewsTask().execute((Void)null); 

 

B, TitleBar通过自定义的ViewFlipper实现,主ListView通过自定义SeparatedListAdapter实现。Jamendo将(标题+标准ListView<自定义 PurpleAdapter>)封装成一个方法,每次调用Add方法,加入一栏及其内容。

 

C, 开始我觉得不够清晰化。Adapter太多,其实只需要一个Adapter。但是:看了项目其他部分对PurpleAdapter的引用之后,觉得Jamendo是想实现代码的复用。另外,Jamendo将点击事件封装在PurpleEntry中也不错。清晰,设置ItemOnClick方法,实现PurpleListener方法。代码如下:

  1. /** 
  2.      * Fills ListView with clickable menu items 
  3.      */ 
  4.     private void fillHomeListView(){ 
  5.         mBrowseJamendoPurpleAdapter = new PurpleAdapter(this); 
  6.         mMyLibraryPurpleAdapter = new PurpleAdapter(this); 
  7.         ArrayList<PurpleEntry> browseListEntry = new ArrayList<PurpleEntry>(); 
  8.         ArrayList<PurpleEntry> libraryListEntry = new ArrayList<PurpleEntry>(); 
  9.          
  10.         // BROWSE JAMENDO 
  11.          
  12.         browseListEntry.add(new PurpleEntry(R.drawable.list_search, R.string.search, new PurpleListener(){ 
  13.             @Override 
  14.             public void performAction() { 
  15.                 SearchActivity.launch(HomeActivity.this); 
  16.             } 
  17.         })); 
  18.  
  19.         browseListEntry.add(new PurpleEntry(R.drawable.list_radio, R.string.radio, new PurpleListener(){ 
  20.             @Override 
  21.             public void performAction() { 
  22.                 RadioActivity.launch(HomeActivity.this); 
  23.             } 
  24.         })); 
  25.          
  26.         browseListEntry.add(new PurpleEntry(R.drawable.list_top, R.string.most_listened, new PurpleListener(){ 
  27.             @Override 
  28.             public void performAction() { 
  29.                 new Top100Task(HomeActivity.this, R.string.loading_top100, R.string.top100_fail).execute(); 
  30.             } 
  31.         })); 
  32.          
  33.         // MY LIBRARY 
  34.          
  35.         libraryListEntry.add(new PurpleEntry(R.drawable.list_playlist, R.string.playlists, new PurpleListener(){ 
  36.             @Override 
  37.             public void performAction() { 
  38.                 BrowsePlaylistActivity.launch(HomeActivity.this, Mode.Normal); 
  39.             } 
  40.         })); 
  41.          
  42.         // check if we have personalized client then add starred albums 
  43.         final String userName = PreferenceManager.getDefaultSharedPreferences(this).getString("user_name"null); 
  44.         if(userName != null && userName.length() > 0){ 
  45.             libraryListEntry.add(new PurpleEntry(R.drawable.list_cd, R.string.albums, new PurpleListener(){ 
  46.                 @Override 
  47.                 public void performAction() { 
  48.                     StarredAlbumsActivity.launch(HomeActivity.this, userName); 
  49.                 } 
  50.             })); 
  51.         } 
  52.          
  53.         /* following needs jamendo authorization (not documented yet on the wiki) 
  54.          * listEntry.add(new PurpleEntry(R.drawable.list_mail, "Inbox"));  
  55.          */ 
  56.  
  57.         // show this list item only if the SD Card is present 
  58.         if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ 
  59.             libraryListEntry.add(new PurpleEntry(R.drawable.list_download, R.string.download, new PurpleListener(){ 
  60.                 @Override 
  61.                 public void performAction() { 
  62.                     DownloadActivity.launch(HomeActivity.this); 
  63.                 } 
  64.             })); 
  65.         } 
  66.          
  67. //      listEntry.add(new PurpleEntry(R.drawable.list_star, R.string.favorites, new PurpleListener(){ 
  68. // 
  69. //          @Override 
  70. //          public void performAction() { 
  71. //              Playlist playlist = new DatabaseImpl(HomeActivity.this).getFavorites(); 
  72. //              JamendroidApplication.getInstance().getPlayerEngine().openPlaylist(playlist); 
  73. //              PlaylistActivity.launch(HomeActivity.this, true); 
  74. //          } 
  75. // 
  76. //      })); 
  77.          
  78.         // attach list data to adapters 
  79.         mBrowseJamendoPurpleAdapter.setList(browseListEntry); 
  80.         mMyLibraryPurpleAdapter.setList(libraryListEntry); 
  81.          
  82.         // separate adapters on one list 
  83.         SeparatedListAdapter separatedAdapter = new SeparatedListAdapter(this); 
  84.         separatedAdapter.addSection(getString(R.string.browse_jamendo), mBrowseJamendoPurpleAdapter); 
  85.         separatedAdapter.addSection(getString(R.string.my_library), mMyLibraryPurpleAdapter); 
  86.          
  87.         mHomeListView.setAdapter(separatedAdapter); 
  88.         mHomeListView.setOnItemClickListener(mHomeItemClickListener); 
  89.     } 

备注:不考虑复用,单一Adapter也可完成分栏式Adapter,见本人博客:

http://mikewang.blog.51cto.com/3826268/737715

(五) 菜单的实现

可以指定条件显示或隐藏菜单的某一项,代码如下:

  1. @Override 
  2.     public boolean onPrepareOptionsMenu(Menu menu) { 
  3.         if(JamendoApplication.getInstance().getPlayerEngineInterface() == null || JamendoApplication.getInstance().getPlayerEngineInterface().getPlaylist() == null){ 
  4.             menu.findItem(R.id.player_menu_item).setVisible(false); 
  5.         } else { 
  6.             menu.findItem(R.id.player_menu_item).setVisible(true); 
  7.         } 
  8.         return super.onPrepareOptionsMenu(menu); 
  9.     }