APP开发流程实例讲解-儒释道网络电台八天开发全程
功能和界面初步设定
APP开发流程实例讲解-儒释道网络电台八天开发全程
昨天的做的设计图是比较简单的,主要麻烦是需要实现两侧的滑动抽屉菜单。
在Android Studio中有一个模板可以创建左侧抽屉,但儒释道网络电台APP需要两边两个抽屉。在网上找到一篇文章《AndroidDrawerLayout+fragment布局实现左右侧滑 》,是使用FragmentTransaction来实现左右侧栏的显现。还有一种办法是使用第三方组件SlidingMenu。难道就不能用Android Studio模板可以创建两侧抽屉滑动菜单吗?经过我一个多小时的探索和尝试,最终发现是可以的。创建的方法我已经写到另一篇文章《Android DrawerLayout+NavigationView布局实现左右两边侧滑菜单 》,这里不再多说。今天主要的完成的工作有。
双侧滑动抽屉菜单
实现方法上面已经说过,但又略有不同。根据昨天设计的界面,两侧菜单其实是三个ListView,但NavigationView主要是结合menu菜单来创建,有些不对路。经过尝试,最终删掉了NavigationView的menu属性,将列表加入到headerLayout中。实际代码如下:
nav_header_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main" />
<android.support.design.widget.NavigationView
android:id="@+id/right_nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_server_header_main" />
</android.support.v4.widget.DrawerLayout>
然后就是在两个header的Layout文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:showDividers="end"
android:divider="@drawable/bottom_line">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:src="@mipmap/ic_launcher"
android:padding="8dp" />
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:paddingBottom="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="android.studio@android.com" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/banner48"
android:showDividers="end"
android:divider="@drawable/bottom_line"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_today"
android:layout_margin="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="节目列表" />
</LinearLayout>
<ListView
android:id="@+id/lv_programs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/banner48"
android:showDividers="end"
android:divider="@drawable/bottom_line"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_compass"
android:layout_margin="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="最新消息" />
</LinearLayout>
<ListView
android:id="@+id/lv_news"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
nav_server_header_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/banner48"
android:showDividers="end"
android:divider="@drawable/bottom_line"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_compass"
android:layout_margin="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="选择线路" />
</LinearLayout>
<ListView
android:id="@+id/lv_servers"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
ListView列表项Layout就不贴了。
主界面的设计
如昨天设计图所示的Layout文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.jianchi.fsp.buddhismnetworkradio.MainActivity"
tools:showIn="@layout/app_bar_main">
<!--640*360-->
<FrameLayout
android:id="@+id/player_frame"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_width="match_parent"
android:layout_height="240dp">
<VideoView
android:layout_width="match_parent"
android:layout_height="240dp"
android:id="@+id/videoView" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/banner48"
android:layout_gravity="bottom"
android:background="#b4b4b4b4"
android:gravity="center_vertical"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="净土大经科注 第318集"
android:id="@+id/textView3"
android:textColor="@android:color/white"
android:layout_weight="1" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="仅声音"
android:id="@+id/checkBox" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/player_frame"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:background="@drawable/side_nav_bar"
android:layout_height="@dimen/banner48"
android:gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_jiangyi"
android:paddingLeft="8dp"
android:paddingRight="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="经文讲义" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scrollView"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text New Text "
android:id="@+id/textView2" />
</ScrollView>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
根据比例设置视频窗口高度
FrameLayout player_frame = (FrameLayout) findViewById(R.id.player_frame);
WindowManager wm = this.getWindowManager();
int width = wm.getDefaultDisplay().getWidth();
int height = width * 9 / 16;
player_frame.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height));
初步完成网络访问API类
网络访问使用了OKHTTP组件,并且是异步访问。OKHttp的使用这里就不多说了。功能实现方法是使用OKHttp下载网页,用正则表达式解析网页,抓取数据生成对象。调用完成事件将数据传回。然后再在UI线程中使用数据填充ListView等。
API类如下
package com.jianchi.fsp.buddhismnetworkradio;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by fsp on 16-7-5.
*/
public class WebApi {
/*
节目时间表:
视频音频播放器
线路选择:http://www.amtb.tw/tvchannel/play-1-revised.asp
最新讯息:http://www.amtb.tw/tvchannel/show_marquee.asp
经文讲义:http://ft.hwadzan.com/mycalendar/mycalendar_embed_livetv.php?calendar_name=livetv
* */
private static final String programsListUrl = "http://ft.hwadzan.com/mycalendar/mycalendar_embed.php?calendar_name=livetv&showview=day&valign=true&bgcolor=none&showtimecolumns=start&tvmenu=3";
private static final String serversListUrl = "http://www.amtb.tw/tvchannel/play-1-revised.asp";
private static final String newsListUrl = "http://www.amtb.tw/tvchannel/show_marquee.asp";
private static final String noteUrl = "http://ft.hwadzan.com/mycalendar/mycalendar_embed_livetv.php?calendar_name=livetv";
Pattern programsListPattern = Pattern.compile("<td class='style1'>\\s*<strong>(.*?)</strong>\\s*(.*?)\\s*</td>");
Pattern htmlTagPattern = Pattern.compile("<[^>]*>");
Pattern serversListPattern = Pattern.compile("serverAddress = \"(.*?)\";\\s*serverName = \"(.*?)\";");
Pattern newsListPattern = Pattern.compile("<div id='bul_\\d'>(.*?)</div>");
Pattern noteItemPattern = Pattern.compile("<p class=\"MsoNormal\">\\s*(.*?)\\s*</p>");
private OkHttpClient client = new OkHttpClient();
IProgramsListEvent programsListEvent;
IServersListEvent serversListEvent;
IProgramsListEvent newsListEvent;
IStringEvent noteEvent;
public void GetNote(IStringEvent noteEvent) {
this.noteEvent = noteEvent;
Request request = new Request.Builder()
.url(noteUrl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
WebApi.this.noteEvent.getMsg(null);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String html = new String(response.body().bytes(), "big5");
Matcher m = noteItemPattern.matcher(html);
StringBuilder sb = new StringBuilder();
while (m.find()){
String nm = m.group(1);
if(nm.startsWith("<")) {
Matcher hm = htmlTagPattern.matcher(nm);
nm = hm.replaceAll("");
}
sb.append(nm).append("\r\n");
}
WebApi.this.noteEvent.getMsg(sb.toString());
}
});
}
public void GetNewsList(IProgramsListEvent newsListEvent) {
this.newsListEvent = newsListEvent;
Request request = new Request.Builder()
.url(newsListUrl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
WebApi.this.newsListEvent.getItems(null);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String html = new String(response.body().bytes(), "big5");
Matcher m = newsListPattern.matcher(html);
List<String> newsList = new ArrayList<String>();
while (m.find()){
String nm = m.group(1);
if(nm.startsWith("<")) {
Matcher hm = htmlTagPattern.matcher(nm);
nm = hm.replaceAll("");
}
newsList.add(nm);
}
WebApi.this.newsListEvent.getItems(newsList);
}
});
}
public void GetProgramsList(IProgramsListEvent programsListEvent) {
this.programsListEvent = programsListEvent;
Request request = new Request.Builder()
.url(programsListUrl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
WebApi.this.programsListEvent.getItems(null);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String html = new String(response.body().bytes(), "utf-8");
Matcher m = programsListPattern.matcher(html);
List<String> programsList = new ArrayList<String>();
while (m.find()){
String nm = m.group(2);
if(nm.startsWith("<")) {
Matcher hm = htmlTagPattern.matcher(nm);
nm = hm.replaceAll("");
}
programsList.add(m.group(1) + " " + nm);
}
WebApi.this.programsListEvent.getItems(programsList);
}
});
}
public void GetServersList(IServersListEvent serversListEvent) {
this.serversListEvent = serversListEvent;
Request request = new Request.Builder()
.url(serversListUrl)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String html = new String(response.body().bytes(), "utf-8");
Matcher m = serversListPattern.matcher(html);
ServersList serverList = new ServersList();
while (m.find()){
serverList.add(new ServerInfo(m.group(1), m.group(2)));
}
WebApi.this.serversListEvent.getServers(serverList);
}
});
}
}
在UI线程中调用的代码如下
api = new WebApi();
api.GetNewsList(new IProgramsListEvent() {
@Override
public void getItems(List<String> programs) {
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
});
api.GetNote(new IStringEvent() {
@Override
public void getMsg(String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
});
api.GetProgramsList(new IProgramsListEvent() {
@Override
public void getItems(List<String> programs) {
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
});
api.GetServersList(new IServersListEvent() {
@Override
public void getServers(ServersList servers) {
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
});
总的来说写代码用的时间比较少,界面设计不熟悉用时较多。原计划每天花2个小时来做这个APP,但今天实际花有4个多小时。界面代码那里花时间过多了,写JAVA代码反而比较快。
明天按计划,写播放器控制的代码。