退出应用时,保存歌曲位置(也就是当前是第几首歌曲)
退出应用时,保存播放模式(也就是用户设置的是顺序播放/随机播放/单曲循环)
进入应用时,读取歌曲位置
进入应用时,读取播放模式
实现PlayActivity(独立音乐播放界面)的专辑图片layout滑动,滑动后展示歌词layout
(目前源码,只实现了专辑图片layout与歌词layout,歌词layout的信息填充后续会进行补充)
截止到目前的源码下载:
http://download.csdn.net/detail/iwanghang/9503338
最后完整的项目源码(打赏5积分请点这边):http://download.csdn.net/detail/iwanghang/9524502
最后完整的项目源码(免积分下载请点这边):http://download.csdn.net/detail/iwanghang/9682928
欢迎移动开发爱好者交流:我的微信是iwanghang
实现效果如图:
滑动中...
滑动后
状态存取实现代码如下:
DRMPlayerApp如下:
package com.iwanghang.drmplayer;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import com.iwanghang.drmplayer.utils.Contant;
/**
* Created by iwanghang on 16/4/26.
*/
public class DRMPlayerApp extends Application{
//SharedPreferences是Android平台上一个轻量级的存储类,用来保存应用的一些常用配置
public static SharedPreferences sp;//SharedPreferences 直译为 共享偏好
@Override
public void onCreate() {
super.onCreate();
//实例化,存储名称为SP_NAME,存储模式为私有
sp = getSharedPreferences(Contant.SP_NAME, Context.MODE_PRIVATE);
//目的,比如在退出Activity时,保存循环模式,歌曲位置(第几首歌曲)
//这里,我在MainActivity的onDestroy时,调用SharedPreferences,保存进度值
}
}
AndroidManiFest如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.iwanghang.drmplayer">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".DRMPlayerApp"
android:allowBackup="true"
android:icon="@mipmap/app_icon"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" />
<activity android:name=".PlayActivity"/>
<service
android:name=".PlayService"
android:enabled="true"
android:exported="true" />
<!--<activity-->
<!--android:name=".PlayActivity"-->
<!--android:label="@string/title_activity_play"></activity>-->
</application>
</manifest>
MainActivity如下:
/*
* Copyright (C) 2013 Andreas Stuetz <andreas.stuetz@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.iwanghang.drmplayer;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import com.astuetz.PagerSlidingTabStrip;
import com.astuetz.viewpager.extensions.sample.QuickContactFragment;
import com.iwanghang.drmplayer.utils.MediaUtils;
import com.iwanghang.drmplayer.vo.Mp3Info;
import java.nio.charset.CoderMalfunctionError;
import java.util.ArrayList;
public class MainActivity extends BaseActivity {
// private final Handler handler = new Handler();
private PagerSlidingTabStrip tabs;
private ViewPager pager;
private MyPagerAdapter adapter;
// private Drawable oldBackground = null;
// private int currentColor = 0xFFC74B46;
private MyMusicListFragment myMusicListFragment;
private NetMusicListFragment netMusicListFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs);
pager = (ViewPager) findViewById(R.id.pager);
adapter = new MyPagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
final int pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources()
.getDisplayMetrics());
pager.setPageMargin(pageMargin);
tabs.setViewPager(pager);
//修改主界面颜色,稍后修复功能,暂时使用默认颜色
//changeColor(currentColor);
//绑定服务
//服务在加载SplashActivity(欢迎页面)的时候,已经启动
//bindPlayService();
//这里,我在MyMusicListFragment里面绑定,而没有在MainActivity里绑定
}
@Override
public void publish(int progress) {
//更新进度条
}
@Override
public void change(int position) {
//更新歌曲位置.按钮的状态等信息
/**
* 本地音乐的播放UI实际上在MyMusicListFragment中,所以要
* 先在MyMusicListFragmen中,写入public void changeUIStatus(int position){}
* 然后,传参过去
*/
if (pager.getCurrentItem()==0){//如果页面等于0,则说明选中的是第一个页面,我的音乐页面
myMusicListFragment.changeUIStatusOnPlay(position);
}else if (pager.getCurrentItem()==1){
}
}
// @Override
// public boolean onCreateOptionsMenu(Menu menu) {
// getMenuInflater().inflate(R.menu.main, menu);
// return true;
// }
//
// @Override
// public boolean onOptionsItemSelected(MenuItem item) {
//
// switch (item.getItemId()) {
//
// case R.id.action_contact:
// QuickContactFragment dialog = new QuickContactFragment();
// dialog.show(getSupportFragmentManager(), "QuickContactFragment");
// return true;
//
// }
//
// return super.onOptionsItemSelected(item);
// }
// private void changeColor(int newColor) {
//
// tabs.setIndicatorColor(newColor);
//
// // change ActionBar color just if an ActionBar is available
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
//
// Drawable colorDrawable = new ColorDrawable(newColor);
// Drawable bottomDrawable = getResources().getDrawable(R.drawable.actionbar_bottom);
// LayerDrawable ld = new LayerDrawable(new Drawable[] { colorDrawable, bottomDrawable });
//
// if (oldBackground == null) {
//
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// ld.setCallback(drawableCallback);
// } else {
// getActionBar().setBackgroundDrawable(ld);
// }
//
// } else {
//
// TransitionDrawable td = new TransitionDrawable(new Drawable[] { oldBackground, ld });
//
// // workaround for broken ActionBarContainer drawable handling on
// // pre-API 17 builds
// // https://github.com/android/platform_frameworks_base/commit/a7cc06d82e45918c37429a59b14545c6a57db4e4
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
// td.setCallback(drawableCallback);
// } else {
// getActionBar().setBackgroundDrawable(td);
// }
//
// td.startTransition(200);
//
// }
//
// oldBackground = ld;
//
// // http://stackoverflow.com/questions/11002691/actionbar-setbackgrounddrawable-nulling-background-from-thread-handler
// getActionBar().setDisplayShowTitleEnabled(false);
// getActionBar().setDisplayShowTitleEnabled(true);
//
// }
//
// currentColor = newColor;
//
// }
// public void onColorClicked(View v) {
//
// int color = Color.parseColor(v.getTag().toString());
// changeColor(color);
//
// }
// @Override
// protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
// outState.putInt("currentColor", currentColor);
// }
// @Override
// protected void onRestoreInstanceState(Bundle savedInstanceState) {
// super.onRestoreInstanceState(savedInstanceState);
// currentColor = savedInstanceState.getInt("currentColor");
// changeColor(currentColor);
// }
// private Drawable.Callback drawableCallback = new Drawable.Callback() {
// @Override
// public void invalidateDrawable(Drawable who) {
// getActionBar().setBackgroundDrawable(who);
// }
//
// @Override
// public void scheduleDrawable(Drawable who, Runnable what, long when) {
// handler.postAtTime(what, when);
// }
//
// @Override
// public void unscheduleDrawable(Drawable who, Runnable what) {
// handler.removeCallbacks(what);
// }
// };
public class MyPagerAdapter extends FragmentPagerAdapter {
private final String[] TITLES = { "我的音乐", "网络音乐"};
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public CharSequence getPageTitle(int position) {
return TITLES[position];
}
@Override
public int getCount() {
return TITLES.length;
}
@Override
public Fragment getItem(int position) {
//return SuperAwesomeCardFragment.newInstance(position);
if(position==0){
if(myMusicListFragment==null){
myMusicListFragment = MyMusicListFragment.newInstance();
}
return myMusicListFragment;
}else if (position == 1){
if(netMusicListFragment==null){
netMusicListFragment = netMusicListFragment.newInstance();
}
return netMusicListFragment;
}
return null;
//return MyMusicListFragment.newInstance();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//保存当前播放的一些状态值
DRMPlayerApp app = (DRMPlayerApp) getApplication();
SharedPreferences.Editor editor = app.sp.edit();
//保存 当前正在播放的歌曲的位置
editor.putInt("currentPosition",playService.getCurrentPosition());
//保存 播放模式
editor.putInt("play_mode",playService.getPlay_mode());
//保存 提交
editor.commit();
//创建DRMPlayerApp继承Application,同时需要在把AndroidManiFest中的public换成DRMPlayerApp
//在DRMPlayerApp的onCreate中 实例化 SharedPreferences
//在MainActivity的onDestroy中 保存状态值
//在PlayService的onCreate中 恢复状态值
}
}
PlayService如下:
package com.iwanghang.drmplayer;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import com.iwanghang.drmplayer.utils.MediaUtils;
import com.iwanghang.drmplayer.vo.Mp3Info;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 音乐播放的服务组件
* 实现功能:
* 播放
* 暂停
* 下一首
* 上一首
* 获取当前歌曲的播放进度
*
* 需要在AndroidManifest.xml添加以下代码:
*<service
*android:name=".PlayService"
*android:enabled="true"
*android:exported="true">
*</service>
*
* 实现功能(播放模式play_mode):
* 顺序播放
* 随机播放
* 单曲循环
*/
public class PlayService extends Service implements OnCompletionListener,OnErrorListener{
private MediaPlayer mPlayer;
private int currentPosition;//当前正在播放的歌曲的位置
ArrayList<Mp3Info> mp3Infos;
private MusicUpdatrListener musicUpdatrListener;
//创建一个单实力的线程,用于更新音乐信息
private ExecutorService es = Executors.newSingleThreadExecutor();
//播放模式
public static final int ORDER_PLAY = 1;//顺序播放
public static final int RANDOM_PLAY = 2;//随机播放
public static final int SINGLE_PLAY = 3;//单曲循环
private int play_mode = ORDER_PLAY;//播放模式,默认为顺序播放
/**
* @param play_mode
* ORDER_PLAY = 1;//顺序播放
* RANDOM_PLAY = 2;//随机播放
* SINGLE_PLAY = 3;//单曲循环
*/
//set方法
public void setPlay_mode(int play_mode) {
this.play_mode = play_mode;
}
//get方法
public int getPlay_mode() {
return play_mode;
}
private boolean isPause = false;//歌曲播放中的暂停状态
public boolean isPause(){
return isPause;
}
public PlayService() {
}
public int getCurrentPosition(){
return currentPosition;
}
private Random random = new Random();//创建随机对象
//MediaPlayer.Completion 播放完成 实现播放下一首功能
//播放完成以后,判断播放模式(曲目循环方式)
//为了实现循环后,可以显示音乐信息,需要在PlayAcivity的change里添加对应代码
@Override
public void onCompletion(MediaPlayer mp) {
switch (play_mode){
case ORDER_PLAY://顺序播放
next();//下一首
break;
case RANDOM_PLAY://随机播放
//currentPosition = random.nextInt(mp3Infos.size());//随机下标为mp3Infos.size()
//play(currentPosition);
play(random.nextInt(mp3Infos.size()));
break;
case SINGLE_PLAY://单曲循环
play(currentPosition);
break;
default:
break;
}
}
//MediaPlayer.Error 播放错误 处理实现播放下一首功能出现的错误
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.reset();//重启
return false;
}
//内部类PlayBinder实现Binder,得到当前PlayService对象
class PlayBinder extends Binder{
public PlayService getPlayService(){
System.out.println("PlayService #1 " + PlayService.this);
return PlayService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return new PlayBinder();//通过PlayBinder拿到PlayService,给Activity调用
}
@Override
public void onCreate() {
super.onCreate();
//恢复状态值
DRMPlayerApp app = (DRMPlayerApp) getApplication();
currentPosition = app.sp.getInt("currentPosition",0);
play_mode = app.sp.getInt("play_mode",PlayService.ORDER_PLAY);
//创建DRMPlayerApp继承Application,同时需要在把AndroidManiFest中的public换成DRMPlayerApp
//在DRMPlayerApp的onCreate中 实例化 SharedPreferences
//在MainActivity的onDestroy中 保存状态值
//在PlayService的onCreate中 恢复状态值
mPlayer = new MediaPlayer();
mPlayer.setOnCompletionListener(this);//注册播放完成事件
mPlayer.setOnErrorListener(this);//注册播放错误事件
mp3Infos = MediaUtils.getMp3Infos(this);//获取Mp3列表
es.execute(updateSteatusRunnable);//更新进度值
}
@Override
public void onDestroy() {
super.onDestroy();
//回收线程
if (es!=null && !es.isShutdown()){//当进度值等于空,并且,进度值没有关闭
es.shutdown();
es = null;
}
}
//利用Runnable来实现多线程
/**
* Runnable
* Java中实现多线程有两种途径:继承Thread类或者实现Runnable接口.
* Runnable接口非常简单,就定义了一个方法run(),继承Runnable并实现这个
* 方法就可以实现多线程了,但是这个run()方法不能自己调用,必须由系统来调用,否则就和别的方法没有什么区别了.
* 好处:数据共享
*/
Runnable updateSteatusRunnable = new Runnable() {//更新状态
@Override
public void run() {
//不断更新进度值
while (true){
//音乐更新监听不为空,并且,媒体播放不为空,并且媒体播放为播放状态
if(musicUpdatrListener!=null && mPlayer!=null && mPlayer.isPlaying()){
musicUpdatrListener.onPublish(getCurrentProgress());//获取当前的进度值
}
try {
Thread.sleep(500);//500毫秒更新一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//播放
public void play(int position){
if (position>=0 && position<mp3Infos.size()){
Mp3Info mp3Info = mp3Infos.get(position);//获取mp3Info对象
//进行播放,播放前判断
try {
mPlayer.reset();//重启
mPlayer.setDataSource(this, Uri.parse(mp3Info.getUrl()));//资源解析,Mp3地址
mPlayer.prepare();//准备
mPlayer.start();//开始(播放)
currentPosition = position;//保存当前位置到currentPosition,比如第一首,currentPosition = 1
} catch (IOException e) {
e.printStackTrace();
}
if(musicUpdatrListener!=null){
musicUpdatrListener.onChange(currentPosition);//更新当前位置
}
}
}
//暂停
public void pause(){
if (mPlayer.isPlaying()){
mPlayer.pause();
isPause = true;
}
}
//下一首
public void next(){
if (currentPosition>=mp3Infos.size()-1){//如果超出最大值,(因为第一首是0),说明已经是最后一首
currentPosition = 0;//回到第一首
}else {
currentPosition++;//下一首
}
play(currentPosition);
}
//上一首 previous
public void prev(){
if (currentPosition-1<0){//如果上一首小于0,说明已经是第一首
currentPosition = mp3Infos.size()-1;//回到最后一首
}else {
currentPosition--;//上一首
}
play(currentPosition);
}
//默认开始播放的方法
public void start(){
if (mPlayer!=null && !mPlayer.isPlaying()){//判断当前歌曲不等于空,并且没有在播放的状态
mPlayer.start();
}
}
//获取当前是否为播放状态,提供给MyMusicListFragment的播放暂停按钮点击事件判断状态时调用
public boolean isPlaying(){
if (mPlayer!=null){
return mPlayer.isPlaying();
}
return false;
}
//获取当前的进度值
public int getCurrentProgress(){
if(mPlayer!=null && mPlayer.isPlaying()){//mPlayer不为空,并且,为播放状态
return mPlayer.getCurrentPosition();
}
return 0;
}
//getDuration 获取文件的持续时间
public int getDuration(){
return mPlayer.getDuration();
}
//seekTo 寻找指定的时间位置 (跳到某个时间点进行播放)
public void seekTo(int msec){
mPlayer.seekTo(msec);
}
//更新状态的接口(PlayService的内部接口),并在BaseActivity中实现
public interface MusicUpdatrListener{//音乐更新监听器
public void onPublish(int progress);//发表进度事件(更新进度条)
public void onChange(int position); //更新歌曲位置.按钮的状态等信息
//声明MusicUpdatrListener后,添加set方法
}
//set方法
public void setMusicUpdatrListener(MusicUpdatrListener musicUpdatrListener) {
this.musicUpdatrListener = musicUpdatrListener;
}
}
——————————我是分割线——————————
界面滑动实现代码如下:
FlingGalleryView如下:
package com.iwanghang.drmplayer.custom;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import com.iwanghang.drmplayer.R;
/**
* Created by iwanghang on 16/4/26.
*/
public class FlingGalleryView extends ViewGroup {
private static final int SNAP_VELOCITY = 1000;
// 记录当前屏幕下标,取值范围是:0 到 getChildCount()-1
private int mCurrentScreen;
private Scroller mScroller;
// 速度追踪器,主要是为了通过当前滑动速度判断当前滑动是否为fling
private VelocityTracker mVelocityTracker;
// 记录滑动时上次手指所处的位置
private float mLastMotionX;
private float mLastMotionY;
// Touch状态值 0:静止 1:滑动
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLLING = 1;
// 记录当前touch事件状态--滑动(TOUCH_STATE_SCROLLING)、静止(TOUCH_STATE_REST 默认)
private int mTouchState = TOUCH_STATE_REST;
// 记录touch事件中被认为是滑动事件前的最大可滑动距离
private int mTouchSlop;
// 手指抛动作的最大速度px/s 每秒多少像素
private int mMaximumVelocity;
// 滚动到指定屏幕的事件
private OnScrollToScreenListener mScrollToScreenListener;
// 自定义touch事件
private OnCustomTouchListener mCustomTouchListener;
//滚动到每个屏幕时是否都要触发OnScrollToScreenListener事件
private boolean isEveryScreen=false;
public FlingGalleryView(Context context) {
super(context);
init();
mCurrentScreen = 0;
}
public FlingGalleryView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlingGalleryView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.FlingGalleryView, defStyle, 0);
mCurrentScreen = a.getInt(R.styleable.FlingGalleryView_defaultScreen, 0);
a.recycle();
init();
}
private void init() {
mScroller = new Scroller(getContext());
final ViewConfiguration configuration = ViewConfiguration
.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
// 保证在同一个屏幕执行一下切屏事件的一些参数
private int count = -1;
private int defaultScreen = -1;
// 当滚动条滑动时调用,startScroll()设置的是参数,实际滑动,在其里执行,
@Override
public void computeScroll() {
// mScroller.computeScrollOffset计算当前新的位置,true表示还在滑动,仍需计算
if (mScroller.computeScrollOffset()) {
// 返回true,说明scroll还没有停止
scrollTo(mScroller.getCurrX(), 0);
if(isEveryScreen)singleScrollToScreen();
postInvalidate();
}
}
// 保证在同一个屏幕执行一下切屏事件
private void singleScrollToScreen() {
final int screenWidth = getWidth();
int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
if (whichScreen > (getChildCount() - 1)) {
return;
}
if (defaultScreen == -1) {
defaultScreen = whichScreen;
count = 1;
} else {
if (defaultScreen == whichScreen && count == 0) {
count = 1;
} else {
if (defaultScreen != whichScreen) {
defaultScreen = whichScreen;
count = 0;
}
}
}
if (count == 0) {
if (mScrollToScreenListener != null) {
mScrollToScreenListener.operation(whichScreen, getChildCount());
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"Workspace can only be used in EXACTLY mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"Workspace can only be used in EXACTLY mode.");
}
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
scrollTo(mCurrentScreen * width, 0);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
int childLeft = 0;
// 横向平铺childView
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
child.setOnTouchListener(childTouchListener);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft, 0, childLeft + childWidth,
child.getMeasuredHeight());
childLeft += childWidth;
}
}
}
// 设定childView的Touch事件返回true,这样可以在parentView中截获touch(即onInterceptTouchEvent)的move,up等事件
private OnTouchListener childTouchListener = new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return true;
}
};
// 在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截
/*
* down事件首先会传递到onInterceptTouchEvent()方法
* 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return
* false,那么后续的move,
* up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
* 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return
* true,那么后续的move,
* up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent
* ()处理,注意,目标view将接收不到任何事件。
* 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent
* ()处理。 如果最终需要处理事件的view
* 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mCustomTouchListener != null) {
mCustomTouchListener.operation(ev);
}
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE)
&& (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
// 计算X方向移动的距离
final int xDiff = (int) Math.abs(x - mLastMotionX);
final int touchSlop = mTouchSlop;
if (xDiff > touchSlop) {
// 移动方向小于45度时即X方向可以移动
if (Math.abs(mLastMotionY - y) / Math.abs(mLastMotionX - x) < 1) {
mTouchState = TOUCH_STATE_SCROLLING;
}
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
: TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (!mScroller.isFinished()) {
// 终止滚动条的滑动动画
mScroller.abortAnimation();
}
mLastMotionX = x;
count = -1;
defaultScreen = -1;
break;
case MotionEvent.ACTION_MOVE:
if (mTouchState == TOUCH_STATE_SCROLLING) {
final float t_width = (getWidth() / 4f);
// 最后一个屏幕向左移动时,不能超过屏幕的4分之一
if (getScrollX() > ((getChildCount() - 1) * getWidth() + t_width)) {
break;
}
// 第一个屏幕向右移动时,不能超过屏幕的4分之一
if (getScrollX() < ((t_width) * -1)) {
break;
}
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);// 使用pix/s为单位
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
// 向右移动
snapToScreen(mCurrentScreen - 1, false);
} else if (velocityX < -SNAP_VELOCITY
&& mCurrentScreen < getChildCount() - 1) {
// 向左移动
snapToScreen(mCurrentScreen + 1, false);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
}
return true;
}
// 计算应该去哪个屏
private void snapToDestination() {
final int screenWidth = getWidth();
// 如果超过屏幕的一半就算是下一个屏
final int whichScreen = (getScrollX() + (screenWidth / 2))/ screenWidth;
snapToScreen(whichScreen, false);
}
// 切换屏幕
private void snapToScreen(int whichScreen, boolean isJump) {
// 判断下一个屏幕是否有效,并纠正
whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
if (getScrollX() != (whichScreen * getWidth())) {
final int delta = whichScreen * getWidth() - getScrollX();
count = -1;
defaultScreen = -1;
// 开始滚动动画
mScroller.startScroll(getScrollX(), 0, delta, 0,
Math.abs(delta) * 2);
final int t_mCurrentScreen = mCurrentScreen;
mCurrentScreen = whichScreen;
// 判断是否在同一个屏幕,不在则执行切换屏幕
if (t_mCurrentScreen != whichScreen) {
// 防止重复执行切换屏幕事件
if (Math.abs(t_mCurrentScreen - whichScreen) == 1 && !isJump) {
doOnScrollToScreen();
}
}
invalidate();
}
}
private void doOnScrollToScreen() {
if (mScrollToScreenListener != null) {
mScrollToScreenListener.operation(mCurrentScreen, getChildCount());
}
}
/**
* 设置切换到的指定下标屏幕0至getChildCount()-1
* */
public void setToScreen(int whichScreen, boolean isAnimation) {
if (isAnimation) {
snapToScreen(whichScreen, true);
} else {
whichScreen = Math.max(0,
Math.min(whichScreen, getChildCount() - 1));
mCurrentScreen = whichScreen;
// 直接滚动到该位置
scrollTo(whichScreen * getWidth(), 0);
if (whichScreen != mCurrentScreen) {
doOnScrollToScreen();
}
invalidate();
}
}
/**
* 设置默认屏幕的下标
* */
public void setDefaultScreen(int defaultScreen) {
mCurrentScreen = defaultScreen;
}
/**
* 获取当前屏幕的下标
* */
public int getCurrentScreen() {
return mCurrentScreen;
}
/**
* 注册滚动到指定屏幕的事件
* */
public void setOnScrollToScreenListener(
OnScrollToScreenListener scrollToScreenListener) {
if (scrollToScreenListener != null) {
this.mScrollToScreenListener = scrollToScreenListener;
}
}
/**
* 注册自定义Touch事件
* */
public void setOnCustomTouchListener(
OnCustomTouchListener customTouchListener) {
if (customTouchListener != null) {
this.mCustomTouchListener = customTouchListener;
}
}
/**
* 滚动到指定屏幕的事件(即切屏事件)
* */
public interface OnScrollToScreenListener {
public void operation(int currentScreen, int screenCount);
}
/**
* 自定义的一个Touch事件
* */
public interface OnCustomTouchListener {
public void operation(MotionEvent event);
}
/**
* 滚动到每个屏幕时是否都要触发OnScrollToScreenListener事件
* */
public void setEveryScreen(boolean isEveryScreen) {
this.isEveryScreen = isEveryScreen;
}
}
activity_music_play如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_horizontal_margin">
<!--<android.support.v4.view.ViewPager-->
<RelativeLayout
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_gravity="center"
android:layout_above="@+id/linearLayout3">
<!-- 自定义滑动页面类的 -->
<com.iwanghang.drmplayer.custom.FlingGalleryView
android:id="@+id/fgv_player_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" >
<include
android:id="@+id/album_image_layout"
layout="@layout/album_image_layout" />
<include
android:id="@+id/lrc_layout"
layout="@layout/lrc_layout" />
</com.iwanghang.drmplayer.custom.FlingGalleryView>
</RelativeLayout>
<!--</android.support.v4.view.ViewPager>-->
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="vertical"
android:layout_alignParentBottom="true">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:orientation="horizontal">
<TextView
android:id="@+id/textView1_start_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="@android:color/darker_gray" />
<SeekBar
android:id="@+id/seekBar1"
android:layout_width="235dp"
android:layout_height="wrap_content"
android:indeterminate="false"/>
<TextView
android:id="@+id/textView1_end_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="@android:color/darker_gray" />
</LinearLayout>
<RelativeLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="10dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView1_play_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/app_music_order"
android:layout_alignBottom="@+id/imageView3_previous"
android:layout_alignParentStart="true" />
<ImageView
android:id="@+id/imageView3_previous"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignTop="@+id/imageView2_play_pause"
android:layout_toLeftOf="@+id/imageView2_play_pause"
android:src="@mipmap/app_music_previous" />
<ImageView
android:id="@+id/imageView2_play_pause"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignTop="@+id/imageView1_next"
android:layout_toLeftOf="@+id/imageView1_next"
android:src="@mipmap/app_music_play" />
<ImageView
android:id="@+id/imageView1_next"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:src="@mipmap/app_music_next" />
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
album_image_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="match_parent"
android:gravity="center"
android:orientation="vertical"
android:id="@+id/album_image_layout"
android:weightSum="1">
<TextView
android:id="@+id/textView1_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="歌名"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@android:color/holo_blue_light"
android:layout_gravity="center_horizontal"
android:layout_above="@+id/textView2"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/imageView1_ablum"
android:layout_width="192dp"
android:layout_height="192dp"
android:scaleType="fitCenter"
android:src="@mipmap/app_icon"
android:layout_below="@+id/textView1_title"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:layout_gravity="center_horizontal" />
<ImageView
android:id="@+id/imageView1_ablum_reflection"
android:layout_width="192dp"
android:layout_height="96dp"
android:layout_gravity="center"
android:layout_below="@+id/imageView1_ablum"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/seekBar1"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="向右滑动查看歌词 → "
android:textColor="@android:color/holo_blue_light"
android:layout_gravity="center_horizontal"
android:id="@+id/textView2" />
</LinearLayout>
lrc_layout如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/lrc_layout">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="歌词"
android:textSize="@android:dimen/app_icon_size"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="← 向左滑动查看专辑封面"
android:textColor="@android:color/holo_blue_light"
android:layout_gravity="center_horizontal"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
重点说明:
SharedPrefer ences是Android平台上一个轻量级的存储类,用来保存应用的一些常用配置
在DRMPlayerApp中,实例化SharedPreferences
//创建DRMPlayerApp继承Application,同时需要在把AndroidManiFest中的public换成DRMPlayerApp
//在DRMPlayerApp的onCreate中 实例化 SharedPreferences
//在MainActivity的onDestroy中 保存状态值
//在PlayService的onCreate中 恢复状态值
实现展示歌词信息,修改了PlayActivity(独立音乐播放界面),在activity_music_play.xml中include了2个layout:album_image_layout和lrc_layout
实现滑动功能,直接拿了一段代码FlingGalleryView,暂时不求甚解的用了,以后慢慢学习。
沈阳或周边城市公司有意开发Android,请与我联系
联系方式
微信:iwanghang
QQ:413711276
邮箱:iwanghang@qq.com
转载请注明出处:http://blog.csdn.net/iwanghang/article/details/51448597
最后完整的项目源码(打赏5积分请点这边):http://download.csdn.net/detail/iwanghang/9524502
最后完整的项目源码(免积分下载请点这边):http://download.csdn.net/detail/iwanghang/9682928
Android开发本地及网络Mp3音乐播放器系列博文汇总:
Android开发本地及网络Mp3音乐播放器(一)前言
Android开发本地及网络Mp3音乐播放器(二)SplashActivity(欢迎界面)
Android开发本地及网络Mp3音乐播放器(三)MainActivity(主界面)
Android开发本地及网络Mp3音乐播放器(四)实现音乐播放
Android开发本地及网络Mp3音乐播放器(五)实现专辑封面图片
Android开发本地及网络Mp3音乐播放器(六)实现独立音乐播放界面
Android开发本地及网络Mp3音乐播放器(七)循环模式与专辑倒影
Android开发本地及网络Mp3音乐播放器(八)状态存取与界面滑动
Android开发本地及网络Mp3音乐播放器(九)音乐收藏与列表切换
Android开发本地及网络Mp3音乐播放器(十)最近播放界面与数据保存更新
Android开发本地及网络Mp3音乐播放器(十一)使用Jsoup组件请求网络,并解析音乐数据
Android开发本地及网络Mp3音乐播放器(十二)创建NetMusicListAdapter、SearchResult显示网络音乐列表
Android开发本地及网络Mp3音乐播放器(十三)网络音乐搜索功能实现,歌名歌手专辑名搜索
Android开发本地及网络Mp3音乐播放器(十四)网络音乐下载功能实现
Android开发本地及网络Mp3音乐播放器(十五)网络音乐及歌词下载功能实现
Android开发本地及网络Mp3音乐播放器(十六)歌词显示及滚动事件实现、ViewPager使用
Android开发本地及网络Mp3音乐播放器(十七)已存在歌曲歌词下载
Android开发本地及网络Mp3音乐播放器(十八)新下载歌曲加载准备SdcardSearchToList
Android开发本地及网络Mp3音乐播放器(十九)通知媒体库更新刚下载的MP3
Android开发本地及网络Mp3音乐播放器(二十)歌曲下载完成后通知主界面更新本地音乐