网络收音机资料收集
? ?最近打算利用業余時間,編寫一個Android的網絡收音機。因為我自己偶爾也喜歡聽聽廣播,所以打算用業余時間編寫一個網絡版收音機。說起收音機,其實在工作中已經編寫過一個,不過那個收音機是需要硬件支持,也就是說需要有特定的收音機芯片才可以使用。因為這個要跟芯片通信,還涉及通信協議。所以無法通用,手機上更加使用不了。所以才打算編寫一個網絡版的收音機。
因為這個項目打算利用業余空閑時間來做,所以進度可能會比較慢一些,目前想法是把它做得完善一些,網上有關網絡收音機的開源項目貌似很少,我找了一下,沒發現有成型的項目。等我把程序框架和基本功能實現了,打算把這個項目做成開源項目。下面是目前實現了簡單的播放測試功能界面,這個只是測試用的第一版,后面會根據功能模塊增加進行調整。
????
?
1、網絡接收模塊
做網絡收音機最核心的功能模塊就是如何接收解析網絡數據,目前網絡電臺的廣播協議主要是使用了mms、rtsp、http等多媒體流。Android上面對這些流媒體支持實在是不怎么樣,不知道是不是競爭對手的原因,畢竟mms是微軟的標準。因此只能找第三方庫實現,最后選了兩種方案:第一使用VLC播放器的解碼庫。前面我也寫了一篇編譯VLC的文章。不過還沒時間分析VLC使用和播放核心。第二種是使用目前開放的第三方庫。最后我暫時選了Vitamio作為解碼庫。
?
2、功能需求
- 支持mms、rtsp、http多媒體流播放
- 支持國內絕大部分可以播放的網絡電臺以及國外部分著名電臺
- 支持收藏電臺
- 支持電臺錄音保存
- 支持音效調節
?
3、UI設計規劃
- 打算使用側滑欄實現電臺列表
- 使用多級列表實現電臺列表管理
- UI界面和數據分離,界面數據由XML提供
- 實現動態音效調節界面
?
4、開發計劃
- 2013-7月:實現播放界面、電臺列表、播放管理、后臺播放
- 2013-8月:實現收藏電臺、電臺錄音、音效調節
- 2013-9月:后續規則中。。。
?
?5、后語
目前的UI還比較粗糙,界面還沒有仔細進行調節,只是為了測試播放電臺功能,先把功能加上去。后續會繼續完善,播放部分模塊現在還在調試,打算把播放部分編寫成獨立的模塊,降低代碼的耦合度方便以后更換播放和解碼核心代碼。界面部分盡量獨立出來。
雖然自己已經做過很多Android方面的程序,不過都是基于工作上的開發。自己開發一個業余項目還是第一次,所以希望能把這項目做得完善點,不過功能上不會加入太多其他不相關的東西進去,現在Android市場上的軟件,一個軟件集成太多不相關的東西,用著復雜,也浪費手機資源。這個收音機只會加入收音機相關功能,而且盡量做到簡潔易用。大家如果有這方面興趣,可以提提意見,看希望有什么功能。
目前我本身的工作很忙,在開發一個新項目,經常要加班,回到家已經10點了。所以只能業余晚上回去才有時間做,所以進度安排比較慢。后續會把開發過程寫成一個系列博客,用到的技術也會進行 一些分析講解。
?
上一篇文章總體規劃了這個項目的情況,今天講講實現電臺列表。今天其實主要想講解的是SlidingMenu,也就是我們平時說的側滑欄,現在很多應用都有用這種UI效果。SlidingMenu側滑欄功能實現的方式很多,可以自己使用ViewGroup實現也可以自己繪圖實現。我這里借用了一個開源項目SlidingMenu,因為我這里不是研究如何實現SlidingMenu,而且為了快速實現這種功能,所以就直接使用這個開源項目的成果。
?
?
?
??
上面就是側滑欄的效果,指定一邊滑動,就可以拉出一個新的界面出來。
?
1、工程里引用SlidingMenu
首先我們說說這個開源工程如何使用,因為SlidingMenu是以工程庫的形式使用,因此我們只需要在我們的工程里面應用這個工程就行。下面說說具體步驟:(這是給初學者看的哈,有經驗的跳過)
?
我們添加SlidingMenu后,可以查看該工程屬性,其中有一個Library的屬性,說明這是一個Library工程。然后只需要在我們使用的工程里添加應用這個外部庫就可以。
?
這里添加外部Library,因為我還引用了另外一個解碼庫Vitamio,所以會有兩個外部庫。
?
2、初始化SlidingMenu
//Edited by mythou//http://www.cnblogs.com/mythou/ private void initChannelMenu(){
//創建SlidingMenu對象mChannelMenu = new SlidingMenu(this);
//設置側滑欄菜單位置,這里在左邊。拉動菜單時,會從左邊彈出mChannelMenu.setMode(SlidingMenu.LEFT);
//設置觸摸的范圍,這里設置全屏mChannelMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
//設置陰影的寬度,查看上面第二張效果圖,靠右邊的位置,有一個陰影過渡。就是這個東西mChannelMenu.setShadowWidthRes(R.dimen.shadow_width);
//這里是陰影效果,可以設置圖片或者一個顏色過渡mChannelMenu.setShadowDrawable(R.drawable.shadow);
//設置后面間距,側滑欄和原來界面間距mChannelMenu.setBehindOffsetRes(R.dimen.slidingmenu_offset);
//邊框的角度,這里指邊界地方mChannelMenu.setFadeDegree(0.35f);
//把側滑欄關聯到當前的ActivitymChannelMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT);
//側滑欄的布局文件mChannelMenu.setMenu(R.layout.channel_slide_menu);}
上面給出了SlidingMenu的詳細初始化配置,我們使用的時候可以根據需要的實際效果配置,達到我們需要的效果。SlidingMenu這個開源工程接口實現很好,可以實現很復雜的配置,同時使用的過程也十分簡單,如果你只是單純需要這種功能,借用這個開源項目是不錯的選擇。如果需要自己實現,也可以借鑒一下這工程。
?
3、彈出和收起SlidingMenu
一幫情況下,只有設置了上面的mChannelMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN) ,我們只要在屏幕上面向右滑動就可以把SlidingMenu拉出來,不過有時候我們也需要實現點擊某個按鈕就可以把它拉出來,就像我們上面的“電臺列表”按鈕,點擊一下會自動彈出SlidingMenu處理,要實現這功能很簡單,只要調動SlidingMenu一個借口即可。
//Edited by mythou//http://www.cnblogs.com/mythou/ mOpenMenuButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v){
//調用SlidingMenu的顯示菜單接口mChannelMenu.showMenu();} });
上面通過調用showMenu()即可顯示菜單。隱藏SlidingMenu同樣只需要調用一個接口
//Edited by mythou//http://www.cnblogs.com/mythou/ @Override public void onBackPressed() {if (mChannelMenu.isMenuShowing()) {
//隱藏SlidingMenu,這里的Content就是我們的主Activity。mChannelMenu.showContent();} else {super.onBackPressed();} }
我這里放在了返回鍵的處理,按下返回鍵的時候,自動收起SlidingMenu菜單。
?
4、總結
SlidingMenu是一個在Git上很火也很實用的項目,通過使用該項目可以快速在我們的項目中使用側滑欄功能,只要按照我上面配置在新工程引用SlidingMenu就可以。另外補充一點,SlidingMenu提供了一個例子,不過需要用到另外一個開源工程ActionBarSherlock ,如果要配置該例子,注意引用這工程。不過我配置的時候遇到不少問題,建議大家按照我上面方法直接新建一個工程,先看看SlidingMenu的使用和效果。
上一篇文章說了使用SlidingMenu開源項目實現側滑欄,今天主要是講解多級列表ExpandableListView的使用,以及如何使用它實現電臺分類管理。ExpandableListView是Android自帶的一個實現多級列表的控件,可以理解為ListView的二維實現。下面將針對如何在項目里面使用ExpandableListView進行講解。
?
ExpandableListView效果圖:
?
?
1、引用ExpandableListView控件
//Edited by mythou//http://www.cnblogs.com/mythou/ <ExpandableListView android:id="@+id/list"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_marginTop="0dp"android:background="@drawable/radio_play_bg2" //配置外層列表的背景熟悉android:divider="@drawable/list_driver" //外層列表分割線android:cacheColorHint="#00000000" //這個用過listView的應該都知道,避免拖動黑屏android:listSelector="@drawable/listview_bg" //外層List列表項效果android:childDivider="@drawable/list_driver" //子列表分割線效果></ExpandableListView>
上面是我們平常在XML里面配置的屬性,這個跟我們一般使用ListView很類似,只是它多了一個子列表項的配置,后面我們會使用代碼配置其他一些屬性。ExpandableListView用法跟我們普通ListView類似,只是分開了父類ListView和子類ListView。ExpandableListView還有其他的一些屬性,有需要可以查看android官網文檔。
?
2、繼承BaseExpandableListAdapter
//Edited by mythou//http://www.cnblogs.com/mythou/ public class ChannelListAdapter extends BaseExpandableListAdapter {//設置contextContext mContext;//............. }
?
繼承BaseExpandableListAdapter,編寫提供數據的適配器
?
?
3、數據適配器
我們使用ListView的時候也知道,ListView需要提供一個數據適配器提供數據,ExpandableListView也同樣需要一個適配器,我們需要重載BaseExpandableListAdapter基類,編寫我們的適配器。其中最主要的是getGroupView()和getChildView()方法,這兩個方法分別返回父類列表和子類列表的列表項視圖。
?
//Edited by mythou//http://www.cnblogs.com/mythou/ @Overridepublic View getGroupView(int groupPosition, boolean isExpanded,View convertView, ViewGroup parent) { //生成一個LinearLayout的布局,這里是外層列表的一個列表項視圖LinearLayout ll = new LinearLayout(mContext);
//設置LinearLayout方向ll.setOrientation(0);
//設置列表前面的LogoImageView logo = new ImageView(mContext);
//這里提供一個圖片文件作為Logologo.setImageResource(logos[groupPosition]);
//設置邊距logo.setPadding(80, 0, 0, 0);
//ImageView添加到LinearLayout里面ll.addView(logo);
//列表項標題TextView textView = getTextView();textView.setTextColor(Color.WHITE);
//獲取標題資源textView.setText(getGroup(groupPosition).toString());ll.addView(textView);return ll;}
?
上面返回的View就是我們外層列表的一個列表項,主要包含一個ImageView和TextView控件,使用LinearLayout布局。當然,你也可以使用一個XML文件來布局然后通過Inflate生成一個View返回。兩種方法都可以,還有一點需要補充的是,提供的圖標資源和Text的文字,這里你可以固定在程序,也可以使用數組提供。我后面因為是關聯了電臺的數據,所以使用了XML來保存電臺數據,然后通過解析XML返回一個ArraryList來提供數據,這個后面我講電臺數據的時候,會仔細講解。
//Edited by mythou//http://www.cnblogs.com/mythou/ @Overridepublic View getChildView(int groupPosition, int childPosition,boolean isLastChild, View convertView, ViewGroup parent) {//創建布局使用的LinearLayoutLinearLayout ll = new LinearLayout(mContext);
//設置布局方向ll.setOrientation(0);
//Logo圖片ImageView generallogo = new ImageView(mContext);generallogo.setImageResource(generallogos[groupPosition][childPosition]);ll.addView(generallogo);
//標題資源TextView textView = getTextView();textView.setText(getChild(groupPosition, childPosition).toString());ll.addView(textView);
//返回LinearLayout作為子視圖return ll;}
上面是二級列表的列表項的視圖構造,跟外層列表的構造幾乎一樣,這里同樣構造了一個LinearLayout的布局視圖。你也可以編寫一個XML的布局,然后加載獲取一個View視圖返回。同樣,這里提供數據的方法,我后面也是改用了XML獲取數據,這里先不多說。
除了上面兩個獲取視圖的方法,剩下幾個獲取選項的列表數目的方法同樣也需要重寫,為列表提供數據。
//Edited by mythou//http://www.cnblogs.com/mythou/ @Overridepublic Object getGroup(int groupPosition) {//返回外層列表的對象return generalsTypes[groupPosition];}@Overridepublic long getGroupId(int groupPosition) {//外層列表當前位置return groupPosition;}@Overridepublic int getChildrenCount(int groupPosition) {//子列表選項數目return generals[groupPosition].length;}@Overridepublic Object getChild(int groupPosition, int childPosition) {//獲取子視圖對象return generals[groupPosition][childPosition];}@Overridepublic long getChildId(int groupPosition, int childPosition) {//子列表當前的位置return childPosition;}
? 這些方法都需要重寫,提供我們列表正確的數據,這個跟一般的ListView的Adapter基本類似,只是多了一個父類列表。我們可以根據實際情況重寫其他方法。不過上面提到的方法,基本都要重寫。才能提供正常的數據給列表使用。
?
?4、總結
今天主要是講解一下電臺列表的分類,這里主要是借助二級列表實現,第一層列表主要是代表電臺分類,第二層列表就是具體每一個分類里面的電臺數據。這樣分級管理,邏輯上比較清新,而且用戶也比較方便查找自己喜歡的電臺。下一篇文章會講解如何通過XML讀取數據然后跟我們的電臺列表綁定在一起。
?
國內外的電臺數據很多,起碼有好幾百,所以把這些數據都寫到代碼里面是不實際的。只能寫成一個數據文件,程序啟動的時候再去加載。保存這些簡單數據,我們肯定會優先使用XML文件,今天講講如何讀取XML里面的數據,然后填充到列表里面。
再把這張老截圖貼出來,方便后面對應查看XML的數據。
?
?
?
1、Android解析XML方法
Android里面讀取XML文件有3種方法,其中兩種是解析XML的常規方法:SAX和文檔對象模型方法。以前我寫C++的時候,最常用的是文檔對象模型方法,因為這個方法遍歷數據很方便,缺點是會把整個文件加載到內存,構建一個文檔的樹模型。對于數據量比較大的文件,比較耗內存。以前就經常使用TinyXML的解析庫,我在博客園第一篇文章就是說如何使用TinyXML庫,O(∩_∩)O哈哈~
SAX方法是事件驅動模型,也就是解析到哪個節點會回調相應方法,你需要做的就是在相應的方法里面編寫你的解析代碼,這個有點是解析速度快,而且不耗內存,不過需要你解析完整個文件。查找靈活性沒有文檔對象模型方便。
Android支持上述兩種方法,TinyXML的解析庫也集成在Android里面。除了上面兩種方法,Android自己修改了一種新的方法來解析XML文件——XmlPullParser,這個新方法是基于SAX方法改進的。傳統SAX是需要解析完整個XML文件,而XmlPullParser是可以中途中斷,停止解析。也就是說只要你獲取了你想要的信息,你就可以停止XML的解析工作,因此速度效率上都不錯。
既然是Android官方的方法,這次就使用XmlPullParser來作為XML的解析(我這里數據量不大,用任何一種方法差別不大)。
?
2、定義XML數據格式
首先我們需要定義我們保存電臺數據的XML格式,這是我定義的一種保存電臺類型,以及電臺類型下面具體電臺數據的XML格式,<ChannelType>標簽代表是什么類型的電臺,包含了ID、名稱、圖標、級別等信息。
<RadioChannel>是具體的電臺數據標簽,保存了電臺名稱、圖標、以及URL等重要信息。
//Edited by mythou//http://www.cnblogs.com/mythou/ <ChannelType ID="1"name="推薦電臺"Icon="fm_icon"Level="1"> <RadioChannelID="001"name="貓撲電臺"Icon="default_channel_icon"Level="2"URL="mms://ting.mop.com/mopradio"/><RadioChannelID="002"name="國際電臺懷舊金曲"Icon="default_channel_icon"Level="2"URL="mms://live.cri.cn/oldies/"/><RadioChannelID="003"name="國際電臺都市流行"Icon="default_channel_icon"Level="2"URL="mms://live.cri.cn/pop/"/></ChannelType>
?
?
3、使用Pull解析XML
//Edited by mythou//http://www.cnblogs.com/mythou/ public boolean getRadioListData(InputStream is, ArrayList<RadioChannelData> ChannelTypeList, ArrayList<ArrayList<RadioChannelData>> finalChanneldata) throws Exception { Log.d(TAG, "parse InputStream="+is);//臨時電臺類型RadioChannelData tempChannelTypeItem = null;//臨時電臺數據對象RadioChannelData tempChannelItem = null; //保存每個頻道類型里面具體電臺數據ArrayList<RadioChannelData> channelDataList = null;//顯示ChannelType stringString channelTypeString="";//由android.util.Xml創建一個XmlPullParser實例 XmlPullParser parser = Xml.newPullParser(); //設置輸入流 并指明編碼方式 parser.setInput(is, "UTF-8"); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) {switch (eventType) {case XmlPullParser.START_DOCUMENT: //處理文檔開始break; case XmlPullParser.START_TAG: if (parser.getName().equals("ChannelType")) {//電臺類型信息tempChannelTypeItem = new RadioChannelData(); tempChannelTypeItem.setChannelID(parser.getAttributeValue(0));tempChannelTypeItem.setChannelName(parser.getAttributeValue(1));tempChannelTypeItem.setChannelICON(parser.getAttributeValue(2));tempChannelTypeItem.setLevel(Integer.valueOf(parser.getAttributeValue(3)));//創建每個類型下的列表channelDataList = new ArrayList<RadioChannelData>();} else if (parser.getName().equals("RadioChannel")) {//具體電臺信息tempChannelItem = new RadioChannelData();tempChannelItem.setChannelID(parser.getAttributeValue(0));tempChannelItem.setChannelName(parser.getAttributeValue(1));tempChannelItem.setChannelICON(parser.getAttributeValue(2));tempChannelItem.setLevel(Integer.valueOf(parser.getAttributeValue(3)));tempChannelItem.setChannelURL(parser.getAttributeValue(4));} break; case XmlPullParser.END_TAG: if (parser.getName().equals("ChannelType")) { //電臺類型列表保存ChannelTypeList.add(tempChannelTypeItem); tempChannelTypeItem = null; //把每個類型電臺列表加入到總數據列表finalChanneldata.add(channelDataList);channelDataList = null;} else if (parser.getName().equals("RadioChannel")){channelDataList.add(tempChannelItem); tempChannelItem = null; }break;case XmlPullParser.END_DOCUMENT:break; } eventType = parser.next(); } //Test mythou 打印讀取的數據Log.d(TAG, "Print all radio channel Type----->"+ChannelTypeList.toString());Log.d(TAG, "Print all radio final channel data----->"+finalChanneldata.toString());return true; }
上面代碼是我用來解析XML的代碼,解析的數據保存到對應的ArrayList里面,當然我在程序里面也定義了響應的數據格式類用來保存數據,使用Pull解析XML很方便也很簡單,這里不做詳細介紹,對此不了解的朋友可以去查看相關文檔,個人感覺使用Pull解析文檔比使用文檔對象模型要方便,起碼遍歷一次數據要快捷很多。
? 補充一點,我這里的圖片文件只保存了圖片文件的名字(不帶后綴),我在程序里面會根據圖片名稱讀取Drawable里面的圖片資源。
界面數據相關的就講到這里,下一次會講解如何控制播放,也就是一個播放器的核心。
轉載于:https://www.cnblogs.com/ltb6w/p/8905730.html
總結
- 上一篇: 智能驾驶发展的前世今生 |既生瑜,何生亮
- 下一篇: 护眼台灯真的护眼吗?为家长推荐四款真正护