音乐播放器-MainFragment分析6

到现在为止还是在讲MainFragment,前面提到的那几个Manager现在只剩下最后一个SlidingDrawerManager,正如电影或者电视剧里面演的那样,重头戏都在后面

首先看下这个UI界面是如何调出来的,在MainFragment里面有这样一个方法:

@Override
	public boolean onTouch(View v, MotionEvent event) {
		int bottomTop = mBottomLayout.getTop();
		System.out.println(bottomTop);
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			oldY = (int) event.getY();
			if (oldY > bottomTop) {
				mSdm.open();
			}
		}
		return true;
	}

这里涉及到了坐标,尤其是用到加速度传感器的时候会重点强调坐标,把手机正着放,左上角是原点,向右是X轴,向下是Y轴,向上是Z轴,都是正向。

然后每个布局(也就是View)都有getTop(),getBottom(),getLeft(),getRight(),会得到相应布局最*的地方,然后比如getTop和getLeft就会得到左上方的坐标。当然这里用到的是getTop(),通过getTop判断Touch的点是否是在最下面的界面,if (oldY > bottomTop)就是判断的是在bottomTop的下面,另外最后的返回值也是值得探究的,关于点击事件的分发通常会讨论这个返回值,总结如下:

 true:
1.告诉Android,MotionEvent对象已被使用,不能再提供给其他方法。
2.还告诉Android,继续将此触摸序列的触摸事件(move,up)发送到此方法。
 false:
1.告诉Android,onTouch()方法未使用该事件,所以Android寻找要调用的下一个方法。
2.告诉Android。不再将此触摸序列的触摸事件(move,up)发送到此方法。

(摘自:http://blog.sina.com.cn/s/blog_74c22b2101012k6t.html

然后我们进入到SlidingDrawerManager的open()方法里面:

public void open() {
		mSliding.setVisibility(View.VISIBLE);
		mSliding.animateOpen();
	}

很简单,就是把visibility设置为显示,并开始动画,于是这就有了个问题,屏幕会先显示这个SlidingDrawerManager然后在让它从下面滑上来,如果我们让它先滑上来在设置visibility的话就造成了滑动效果看不见,于是其实较好的 方法就是让这个SlidingDrawer,先处于屏幕的下方一个不可见的区域,或者用dialog或者用Activity封装下。理论上讲就是这样的,以后有时间会重写下,最近在忙着研究架构,毕竟技术就像文章一样,“天下文章一大抄”,从APi设计出来就剩下抄的任务了。

然后是怎么close的呢?它有个自己的handle,点击handle就close了,这个handle是在xml布局文件里面就设置的,代码里面不用管,但作者为了大家能够更清楚些,写了个这个方法:

public void close() {
		mSliding.animateClose();
	}

尽管这个方法没有被调用,也用不着被调用,我们多少还是多知道点好,作者把SlidingDrawer重新写了下,但这个方法没有复写。

接下来就是看里面的其他方法了,里面最关键的就是歌词,所以最后再说。

initView(),refreshSeekProgress(),findViewById(),refreshFavorite(),onClick(),showLrcDialog(),setMusicTimer(),都是些望名知义的方法,

refreshUI()尽管也是望名知义,但这里没有指代UI中的哪些控件,其实这个refresh类似于初始化的意思,所以会在每次显示slidingDrawer的时候调用,这里初始化的内容就是传入的参数以及该方法所调用的refreshSeekProgress()方法,所传入的参数也是由调用方法产生的。

showPlay()名字起得很抽象,意思指的是显示Play和Pause按钮的显示状态(GONE,VISIBILITY,INVISIBILITY)。

onProgressChanged调整音乐进度。在调整期间,手按下Seekbar会调用onStartTrackingTouch方法,此时会停止计时并停止音乐的播放并更新播放状态,手抬起后调用onStopTrackingTouch方法,恢复播放和计时并更新播放状态。

loadLyric(),loadLyricByHand(),....mLyricListener这些涉及到歌词的方法放到后面再说。

startAnimation方法是当你对一首歌点击“我喜欢”的红心按钮后出现的动画。往右下角移动并变大。

onDrawerClosed和onDrawerOpened方法是实现的接口,在构造方法中设置了监听器,通过该监听器可以实现在抽屉弹出和收回时执行的动作。

歌词:

首先看看歌词的显示的布局文件,在TextView里面的Visibility是GONE

 <FrameLayout
        android:id="@+id/player_frame_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/bottomLayout"
        android:layout_below="@id/modeLayout" >

        <ListView
            android:id="@+id/lyricshow"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:descendantFocusability="blocksDescendants"
            android:divider="@color/transparent"
            android:gravity="center"
            android:scrollbars="none" />

        <TextView
            android:id="@+id/lyric_empty"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:padding="15dip"
            android:text="没有歌词,点击手动下载"
            android:textColor="@color/white"
            android:textIsSelectable="false"
            android:textSize="18sp"
            android:visibility="gone" />
    </FrameLayout>

在listview里面属性android:descendantFocusability有3个值这里表示 blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点,摘自:http://www.cnblogs.com/eyu8874521/archive/2012/10/17/2727882.html,这样每一个Text就不会获取焦点了。

该listview有一个ID,lyricshow,如何查找,该控件是在哪段代码中出现的呢?在eclipse的菜单栏上找到search---->search,或者CTRL+H,点击File Search,最后,按照下图填写或者勾选

从下往上的箭头依次代表:工作集,在当前的工程,搜索的范围(该工程所有.java和.xml文件),搜索的单词。最后我们search

我们只需知道java文件里面的就可以了,于是我们找到了,按照目录树展开后如下图,点击该.java文件,然后再在里面CTRL+F,输入查找的单词


找到该控件然在.java文件里面的名称,然后我们就可以找关于该控件的操作,

                mLrcListView.setAdapter(mLyricAdapter);
		mLrcListView.setEmptyView(mLrcEmptyView);
		mLrcListView.startAnimation(AnimationUtils.loadAnimation(mActivity,
				android.R.anim.fade_in));

该3个方法望名知义即可知道该3个方法的行为,在此就不过多啰嗦,其中setEmptyView的官方解释是:Sets the view to show if the adapter is empty

用到该listview的地方还有一个成员对象里面,该对象实现了LyricListener的方法,

private LyricListener mLyricListener = new LyricListener() {

		@Override
		public void onLyricLoaded(List<LyricSentence> lyricSentences, int index) {
			// Log.i(TAG, "onLyricLoaded");
			if (lyricSentences != null) {
				// Log.i(TAG, "onLyricLoaded--->歌词句子数目=" + lyricSentences.size()
				// + ",当前句子索引=" + index);
				mLyricAdapter.setLyric(lyricSentences);
				mLyricAdapter.setCurrentSentenceIndex(index);
				mLyricAdapter.notifyDataSetChanged();
				
			}
		}
		/**
		 * smoothScrollToPositionFromTop让哪一条处于哪个位置,让indexOfCurSentence,处于mLrcListView.getHeight() / 2的位置
		 */
		@Override
		public void onLyricSentenceChanged(int indexOfCurSentence) {
			// Log.i(TAG, "onLyricSentenceChanged--->当前句子索引=" +
			// indexOfCurSentence);
			mLyricAdapter.setCurrentSentenceIndex(indexOfCurSentence);
			mLyricAdapter.notifyDataSetChanged();
			mLrcListView.smoothScrollToPositionFromTop(indexOfCurSentence,
					mLrcListView.getHeight() / 2, 500);
		}
	};

第一个方法是给listview设置数据源和当前歌词索引并更新UI,于是第一个就在初始化界面的时候调用,方法名很明显了,第二个方法是设置当前歌词的位置更新UI,最重要的就是mLrcListView.smoothScrollToPositionFromTop(indexOfCurSentence, mLrcListView.getHeight() / 2, 500);这是歌词变化的起点,通常listview都是我们自己手触动的后滑动的,listview内部有实现当手滑动多少listview滑动多少的代码,通常我么使用2各个函数控制滑动,另外一个是setSelection(int position),望名知义,smoothScrollToPositionFromTop方法有3个参数,第一个参数是listview显示的最上方的position,第二个参数是把最上方这个位置移动到的位置,也就是屏幕的一半,第三个参数是这个UI动作的执行时间。跟着该Listener对象我们查找调用过程。

找到LyricLoadHelper这个类,该类的public方法有6个,但是只有3个被用到,setLyricListener,loadLyric,notifyTime,在后面的2个方法中调用

loadLyric:该方法主要从本地加载歌词,传入文件名,返回加载成功true,如果没有文件或者异常就是false,在方法体里面把每一行的内容解析成每一个Sentence对象(开始时间、持续时间、句子)。然后通过该LyricListener对象的onLyricLoad方法将歌词集合加载到listView里面。

notifyTime:该方法就是通过封装onLyricSentenceChanged方法,用途也与onLyricSentenceChanged差不多,主要是给onLyricSentenceChanged传递参数。

既然看到了notifyTime方法,就看一下它的调用处,依旧是CTRL+ALT+H,它在SlidingdrawerManager的refreshSeekbar中调用,也就是说,歌词的更新和Seekbar的更新是绑定的,这样就不用再写到Handler的handleMessage里面了。究竟哪种好呢?这种写在调用的底层会给架构的简洁带来好处,但refreshSeekbar和refreshLyric是2个动作。

回到该blog开始的地方SlidingDrawerManager里面,看看剩下的关于lyric(歌词)的方法:

loadLyric(MusicInfo playingSong)方法:该方法主要的就是一个if和else,条件是本地有歌词,有就加载没有就通过sharepreference的“是否自动下载歌词”来判断是否下载,是就调用LyricDownloadAsyncTask下载。

loadlyric和loadLyricByHand的区别是后者不是音乐播放的时候执行的,而是放到了弹出的dialog里面的clicklistener方法里面,也就是说如果音乐启动的时候没有网或者在设置里面没有勾选自动下载歌词,那么歌词就必须手动下载,这就是该应用的业务逻辑的一部分。读代码就需要读里面的架构设计。当API被设计出来后,剩下的工作就是抄代码和设计架构了(有点抽象哦这句话)。

LyricDownloadAyncTask类如下:

class LyricDownloadAsyncTask extends AsyncTask<String, Void, String> {

		@Override
		protected String doInBackground(String... params) {
			// 从网络获取歌词,然后保存到本地
			String lyricFilePath = mLyricDownloadManager.searchLyricFromWeb(
					params[0], params[1], mCurrentMusicInfo.musicName);
			// 返回本地歌词路径
			mIsLyricDownloading = false;
			return lyricFilePath;
		}

		@Override
		protected void onPostExecute(String result) {
			// Log.i(TAG, "网络获取歌词完毕,歌词保存路径:" + result);
			// 读取保存到本地的歌曲
			mLyricLoadHelper.loadLyric(result);
		};
	};

凡是网络任务之类的耗时任务就必须放到异步的线程里面,创建的方法比如Thread/Runnable,AsyncTask,Service等,这就引申出了handler用来处理UI变化。

handler是一个非常重要的机制,除了处理UI外,每当我们程序出错时总是有一大堆异常,而前面的错误总有Handler,每个线程只能有一个looper,所以如果有多个handler的时候必须共享一个looper。一个android应用程序默认只有一个线程成为UI线程。

service处于后台进程中没有和UI线程同一个进程中自然也不会阻塞UI线程。

AsyncTask的好处有一点就是里面能够在执行完后台线程后调用onPostExecute()方法将后天的内容设置到前台的UI界面里,这是普通的Thread和Runnable所不具有的。

这里涉及到了网络通信,通过LyricDownloadManager下载歌词,该类的流程是先通过歌曲名和艺术家名获取歌曲的id然后通过id构造哦URL,最后再将歌曲的内容存储在本地,返回本地文件的地址,通过异步任务从本地加载到界面。

获取歌曲ID:

HttpURLConnection httpConn = (HttpURLConnection) mUrl
					.openConnection();
			httpConn.setReadTimeout(mTimeOut);
			if (httpConn.getResponseCode() != HttpURLConnection.HTTP_OK) {
				Log.i(TAG, "http连接失败");
				return null;
			}
			httpConn.connect();
			Log.i(TAG, "http连接成功");

			// 将百度音乐盒的返回的输入流传递给自定义的XML解析器,解析出歌词的下载ID
			mDownloadLyricId = mLyricXMLParser.parseLyricId(httpConn
					.getInputStream());
			httpConn.disconnect();
获取歌曲内容,由于前后差的时间不是太长,就没判断连接是否可用,直接通过url获取流
// 建立网络连接
			br = new BufferedReader(new InputStreamReader(mUrl.openStream(),
					GB2312));
			if (br != null) {
				content = new StringBuilder();
				// 逐行获取歌词文本
				while ((temp = br.readLine()) != null) {
					content.append(temp);
					Log.i(TAG, "<Lyric>" + temp);
				}
				br.close();
			}

有人问?我怎么知道它的数据格式去解析,我怎么知道它的url去创建连接,百度音乐盒里面有api说明。

尽管本人没有尝试过,但微信sdk接入和基于聚合数据的天气预报的小demo就是这么干的,api文档会告诉你api的说明,官方也会给demo和如何使用 api文档的介绍。











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值