`

Android MediaController

阅读更多
android 的mediaPlayer 有自带的MediaControl.我们不比自己重写这个控件:
A view containing controls for a MediaPlayer. Typically contains the buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress slider. It takes care of synchronizing the controls with the state of the MediaPlayer.

The way to use this class is to instantiate it programatically. The MediaController will create a default set of controls and put them in a window floating above your application. Specifically, the controls will float above the view specified with setAnchorView(). The window will disappear if left idle for three seconds and reappear when the user touches the anchor view.

Functions like show() and hide() have no effect when MediaController is created in an xml layout. MediaController will hide and show the buttons according to these rules:

The "previous" and "next" buttons are hidden until setPrevNextListeners() has been called
The "previous" and "next" buttons are visible but disabled if setPrevNextListeners() was called with null listeners
The "rewind" and "fastforward" buttons are shown unless requested otherwise by using the MediaController(Context, boolean) constructor with the boolean set to false




package android.widget;

import android.content.Context;
import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.SeekBar.OnSeekBarChangeListener;

import com.android.internal.policy.PolicyManager;

import java.util.Formatter;
import java.util.Locale;

/**
* A view containing controls for a MediaPlayer. Typically contains the buttons
* like "Play/Pause", "Rewind", "Fast Forward" and a progress slider. It takes
* care of synchronizing the controls with the state of the MediaPlayer.
* <p>
* The way to use this class is to instantiate it programatically. The
* MediaController will create a default set of controls and put them in a
* window floating above your application. Specifically, the controls will float
* above the view specified with setAnchorView(). The window will disappear if
* left idle for three seconds and reappear when the user touches the anchor
* view.
* <p>
* Functions like show() and hide() have no effect when MediaController is
* created in an xml layout.
*
* MediaController will hide and show the buttons according to these rules:
* <ul>
* <li>The "previous" and "next" buttons are hidden until setPrevNextListeners()
* has been called
* <li>The "previous" and "next" buttons are visible but disabled if
* setPrevNextListeners() was called with null listeners
* <li>The "rewind" and "fastforward" buttons are shown unless requested
* otherwise by using the MediaController(Context, boolean) constructor with the
* boolean set to false
* </ul>
*/
public class MediaController extends FrameLayout {

private MediaPlayerControl mPlayer;
private Context mContext;
private View mAnchor;
private View mRoot;
private WindowManager mWindowManager;
private Window mWindow;
private View mDecor;
private ProgressBar mProgress;
private TextView mEndTime, mCurrentTime;
private boolean mShowing;
private boolean mDragging;
private static final int sDefaultTimeout = 3000;
private static final int FADE_OUT = 1;
private static final int SHOW_PROGRESS = 2;
private boolean mUseFastForward;
private boolean mFromXml;
private boolean mListenersSet;
private View.OnClickListener mNextListener, mPrevListener;
StringBuilder mFormatBuilder;
Formatter mFormatter;
private ImageButton mPauseButton;
private ImageButton mFfwdButton;
private ImageButton mRewButton;
private ImageButton mNextButton;
private ImageButton mPrevButton;

public MediaController(Context context, AttributeSet attrs) {
super(context, attrs);
mRoot = this;
mContext = context;
mUseFastForward = true;
mFromXml = true;
}

@Override
public void onFinishInflate() {
if (mRoot != null)
initControllerView(mRoot);
}
/×在这里设置是否使用FastForward而不是Next;useFastForward=false时使用   Next/Prevouse按钮所以我们在实例化MediaControl是调用这个构造函数,并且 useFastForward=false×/
public MediaController(Context context, boolean useFastForward) {
super(context);
mContext = context;
mUseFastForward = useFastForward;
initFloatingWindow();
}

public MediaController(Context context) {
super(context);
mContext = context;
mUseFastForward = true;
initFloatingWindow();
}

private void initFloatingWindow() {
mWindowManager = (WindowManager) mContext.getSystemService("window");
mWindow = PolicyManager.makeNewWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mDecor = mWindow.getDecorView();
mDecor.setOnTouchListener(mTouchListener);
mWindow.setContentView(this);
mWindow.setBackgroundDrawableResource(android.R.color.transparent);

// While the media controller is up, the volume control keys should
// affect the media stream type
mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);

setFocusable(true);
setFocusableInTouchMode(true);
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
requestFocus();
}

private OnTouchListener mTouchListener = new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mShowing) {
hide();
}
}
return false;
}
};

public void setMediaPlayer(MediaPlayerControl player) {
mPlayer = player;
updatePausePlay();
}

/**
* Set the view that acts as the anchor for the control view. This can for
* example be a VideoView, or your Activity's main view.
*
* @param view
*            The view to which to anchor the controller when it is visible.
*/
public void setAnchorView(View view) {
mAnchor = view;

FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT);

removeAllViews();
View v = makeControllerView();
addView(v, frameParams);
}

/**
* Create the view that holds the widgets that control playback. Derived
* classes can override this to create their own.
*
* @return The controller view.
* @hide This doesn't work as advertised
*/
protected View makeControllerView() {
LayoutInflater inflate = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRoot = inflate.inflate(com.android.internal.R.layout.media_controller,
null);

initControllerView(mRoot);

return mRoot;
}

private void initControllerView(View v) {
mPauseButton = (ImageButton) v
.findViewById(com.android.internal.R.id.pause);
if (mPauseButton != null) {
mPauseButton.requestFocus();
mPauseButton.setOnClickListener(mPauseListener);
}

mFfwdButton = (ImageButton) v
.findViewById(com.android.internal.R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
if (!mFromXml) {
mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE
: View.GONE);
}
}

mRewButton = (ImageButton) v
.findViewById(com.android.internal.R.id.rew);
if (mRewButton != null) {
mRewButton.setOnClickListener(mRewListener);
if (!mFromXml) {
mRewButton.setVisibility(mUseFastForward ? View.VISIBLE
: View.GONE);
}
}

// By default these are hidden. They will be enabled when
// setPrevNextListeners() is called
mNextButton = (ImageButton) v
.findViewById(com.android.internal.R.id.next);
if (mNextButton != null && !mFromXml && !mListenersSet) {
mNextButton.setVisibility(View.GONE);
}
mPrevButton = (ImageButton) v
.findViewById(com.android.internal.R.id.prev);
if (mPrevButton != null && !mFromXml && !mListenersSet) {
mPrevButton.setVisibility(View.GONE);
}

mProgress = (ProgressBar) v
.findViewById(com.android.internal.R.id.mediacontroller_progress);
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
seeker.setOnSeekBarChangeListener(mSeekListener);
}
mProgress.setMax(1000);
}

mEndTime = (TextView) v.findViewById(com.android.internal.R.id.time);
mCurrentTime = (TextView) v
.findViewById(com.android.internal.R.id.time_current);
mFormatBuilder = new StringBuilder();
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());

installPrevNextListeners();
}

/**
* Show the controller on screen. It will go away automatically after 3
* seconds of inactivity.
*/
public void show() {
show(sDefaultTimeout);
}

/**
* Show the controller on screen. It will go away automatically after
* 'timeout' milliseconds of inactivity.
*
* @param timeout
*            The timeout in milliseconds. Use 0 to show the controller
*            until hide() is called.
*/

//我们可以在这里设置MediaControl显示的位置:由P.x与P.y决定
public void show(int timeout) {

if (!mShowing && mAnchor != null) {
setProgress();

int[] anchorpos = new int[2];
mAnchor.getLocationOnScreen(anchorpos);

WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = Gravity.TOP;
p.width = mAnchor.getWidth();
p.height = LayoutParams.WRAP_CONTENT;
p.x = 0;
p.y = anchorpos[1] + mAnchor.getHeight() - p.height;

p.format = PixelFormat.TRANSLUCENT;
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
p.token = null;
p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
mWindowManager.addView(mDecor, p);
mShowing = true;
}
updatePausePlay();

// cause the progress bar to be updated even if mShowing
// was already true. This happens, for example, if we're
// paused with the progress bar showing the user hits play.
mHandler.sendEmptyMessage(SHOW_PROGRESS);

Message msg = mHandler.obtainMessage(FADE_OUT);
if (timeout != 0) {
mHandler.removeMessages(FADE_OUT);
mHandler.sendMessageDelayed(msg, timeout);
}
}

public boolean isShowing() {
return mShowing;
}

/**
* Remove the controller from the screen.
*/
public void hide() {
if (mAnchor == null)
return;

if (mShowing) {
try {
mHandler.removeMessages(SHOW_PROGRESS);
mWindowManager.removeView(mDecor);
} catch (IllegalArgumentException ex) {
Log.w("MediaController", "already removed");
}
mShowing = false;
}
}

private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
int pos;
switch (msg.what) {
case FADE_OUT:
hide();
break;
case SHOW_PROGRESS:
pos = setProgress();
if (!mDragging && mShowing && mPlayer.isPlaying()) {
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, 1000 - (pos % 1000));
}
break;
}
}
};

private String stringForTime(int timeMs) {
int totalSeconds = timeMs / 1000;

int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;

mFormatBuilder.setLength(0);
if (hours > 0) {
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds)
.toString();
} else {
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
}
}

private int setProgress() {
if (mPlayer == null || mDragging) {
return 0;
}
int position = mPlayer.getCurrentPosition();
int duration = mPlayer.getDuration();
if (mProgress != null) {
if (duration > 0) {
// use long to avoid overflow
long pos = 1000L * position / duration;
mProgress.setProgress((int) pos);
}
int percent = mPlayer.getBufferPercentage();
mProgress.setSecondaryProgress(percent * 10);
}

if (mEndTime != null)
mEndTime.setText(stringForTime(duration));
if (mCurrentTime != null)
mCurrentTime.setText(stringForTime(position));

return position;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
show(sDefaultTimeout);
return true;
}

@Override
public boolean onTrackballEvent(MotionEvent ev) {
show(sDefaultTimeout);
return false;
}

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
if (event.getRepeatCount() == 0
&& event.isDown()
&& (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE)) {
doPauseResume();
show(sDefaultTimeout);
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) {
if (mPlayer.isPlaying()) {
mPlayer.pause();
updatePausePlay();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
// don't show the controls for volume adjustment
return super.dispatchKeyEvent(event);
} else if (keyCode == KeyEvent.KEYCODE_BACK
|| keyCode == KeyEvent.KEYCODE_MENU) {
hide();

return true;
} else {
show(sDefaultTimeout);
}
return super.dispatchKeyEvent(event);
}

private View.OnClickListener mPauseListener = new View.OnClickListener() {
public void onClick(View v) {
doPauseResume();
show(sDefaultTimeout);
}
};

private void updatePausePlay() {
if (mRoot == null)
return;

ImageButton button = (ImageButton) mRoot
.findViewById(com.android.internal.R.id.pause);
if (button == null)
return;

if (mPlayer.isPlaying()) {
button
.setImageResource(com.android.internal.R.drawable.ic_media_pause);
} else {
button
.setImageResource(com.android.internal.R.drawable.ic_media_play);
}
}

private void doPauseResume() {
if (mPlayer.isPlaying()) {
mPlayer.pause();
} else {
mPlayer.start();
}
updatePausePlay();
}

// There are two scenarios that can trigger the seekbar listener to trigger:
//
// The first is the user using the touchpad to adjust the posititon of the
// seekbar's thumb. In this case onStartTrackingTouch is called followed by
// a number of onProgressChanged notifications, concluded by
// onStopTrackingTouch.
// We're setting the field "mDragging" to true for the duration of the
// dragging
// session to avoid jumps in the position in case of ongoing playback.
//
// The second scenario involves the user operating the scroll ball, in this
// case there WON'T BE onStartTrackingTouch/onStopTrackingTouch
// notifications,
// we will simply apply the updated position without suspending regular
// updates.
private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
public void onStartTrackingTouch(SeekBar bar) {
show(3600000);

mDragging = true;

// By removing these pending progress messages we make sure
// that a) we won't update the progress while the user adjusts
// the seekbar and b) once the user is done dragging the thumb
// we will post one of these messages to the queue again and
// this ensures that there will be exactly one message queued up.
mHandler.removeMessages(SHOW_PROGRESS);
}

public void onProgressChanged(SeekBar bar, int progress,
boolean fromuser) {
if (!fromuser) {
// We're not interested in programmatically generated changes to
// the progress bar's position.
return;
}

long duration = mPlayer.getDuration();
long newposition = (duration * progress) / 1000L;
mPlayer.seekTo((int) newposition);
if (mCurrentTime != null)
mCurrentTime.setText(stringForTime((int) newposition));
}

public void onStopTrackingTouch(SeekBar bar) {
mDragging = false;
setProgress();
updatePausePlay();
show(sDefaultTimeout);

// Ensure that progress is properly updated in the future,
// the call to show() does not guarantee this because it is a
// no-op if we are already showing.
mHandler.sendEmptyMessage(SHOW_PROGRESS);
}
};

@Override
public void setEnabled(boolean enabled) {
if (mPauseButton != null) {
mPauseButton.setEnabled(enabled);
}
if (mFfwdButton != null) {
mFfwdButton.setEnabled(enabled);
}
if (mRewButton != null) {
mRewButton.setEnabled(enabled);
}
if (mNextButton != null) {
mNextButton.setEnabled(enabled && mNextListener != null);
}
if (mPrevButton != null) {
mPrevButton.setEnabled(enabled && mPrevListener != null);
}
if (mProgress != null) {
mProgress.setEnabled(enabled);
}

super.setEnabled(enabled);
}

private View.OnClickListener mRewListener = new View.OnClickListener() {
public void onClick(View v) {
int pos = mPlayer.getCurrentPosition();
pos -= 5000; // milliseconds
mPlayer.seekTo(pos);
setProgress();

show(sDefaultTimeout);
}
};

private View.OnClickListener mFfwdListener = new View.OnClickListener() {
public void onClick(View v) {
int pos = mPlayer.getCurrentPosition();
pos += 15000; // milliseconds
mPlayer.seekTo(pos);
setProgress();

show(sDefaultTimeout);
}
};

private void installPrevNextListeners() {
if (mNextButton != null) {
mNextButton.setOnClickListener(mNextListener);
mNextButton.setEnabled(mNextListener != null);
}

if (mPrevButton != null) {
mPrevButton.setOnClickListener(mPrevListener);
mPrevButton.setEnabled(mPrevListener != null);
}
}

public void setPrevNextListeners(View.OnClickListener next,
View.OnClickListener prev) {
mNextListener = next;
mPrevListener = prev;
mListenersSet = true;

if (mRoot != null) {
installPrevNextListeners();

if (mNextButton != null && !mFromXml) {
mNextButton.setVisibility(View.VISIBLE);
}
if (mPrevButton != null && !mFromXml) {
mPrevButton.setVisibility(View.VISIBLE);
}
}
}

public interface MediaPlayerControl {
void start();

void pause();

int getDuration();

int getCurrentPosition();

void seekTo(int pos);

boolean isPlaying();

int getBufferPercentage();
};
}

分享到:
评论
2 楼 追求幸福 2013-12-16  
fyc0109 写道
麻烦问下,这个如何自定义布局.

我之前自定义布局是这样做的:先看懂它原来的代码,然后把它相关的代码复制到自己的工程里面变成MyMediaControl,然后修改相关的XML,在相对应的地方模仿原来的方式增加一些监听事件
1 楼 fyc0109 2013-06-24  
麻烦问下,这个如何自定义布局.

相关推荐

Global site tag (gtag.js) - Google Analytics