I have a Fragment within a FrameLayout. The Fragment contains a VideoView amongst other things. If I modify the FrameLayout's left position (by calling its layout(...) method), the FrameLayout including the Fragment showing within it re-position themselves correctly, all except the VideoView, which sometimes re-positions itself but most often stays put. Anyone know why possibly the VideoView is not re-positioning itself along with its parent, and how I might be able to get it to do so?
Gave up in the end. Doesn't seem to be a straightforward workaround for re-positioning a VideoView, or any workaround for that matter. What I'm doing now instead is to play the video full screen in a new activity.
Try to use this source code for you VideoView.
public class CustomMediaController extends FrameLayout {
/**
* Public interface to handle FullScreenRequest. User can implement this
* interface to make the VideoPlayer Change to FullScreen. The user must
* handle show/hide of views. If the user do not add a Handler the
* FullScreen control button will be hided.
*
* #author Anis
*
*/
public interface FullScreenMediaPlayer {
/**
* Event callback when the MediaPlayer request the FullScreen.
*/
public void onFullScreenRequest();
/**
* Event callback when the MediaPlayer request the go back to normal
* size.
*/
public void onStreatchScreenRequest();
/**
* CallBackEvent when the MediaPlayer is Playing
*/
public void onPlaying();
}
private MediaPlayerControl mPlayer;
private final Context mContext;
private View mAnchor;
private View mRoot;
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 final 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;
// Add Streach colomn
private ImageButton mFullScreenButton;
private boolean isFullSreen;
private FullScreenMediaPlayer fullScreenListener;
public CustomMediaController(Context context, AttributeSet attrs) {
super(context, attrs);
mRoot = this;
mContext = context;
mUseFastForward = true;
mFromXml = true;
isFullSreen = false;
}
#Override
public void onFinishInflate() {
if (mRoot != null)
initControllerView(mRoot);
}
public CustomMediaController(Context context, boolean useFastForward) {
super(context);
mContext = context;
mUseFastForward = useFastForward;
initFloatingWindow();
}
public CustomMediaController(Context context) {
super(context);
mContext = context;
mUseFastForward = true;
initFloatingWindow();
}
private void initFloatingWindow() {
mDecor = this;
mDecor.setOnTouchListener(mTouchListener);
setFocusable(true);
setFocusableInTouchMode(true);
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
requestFocus();
}
private final OnTouchListener mTouchListener = new OnTouchListener() {
#Override
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(R.layout.media_controller, null);
initControllerView(mRoot);
return mRoot;
}
private void initControllerView(View v) {
mPauseButton = (ImageButton) v.findViewById(R.id.pause);
if (mPauseButton != null) {
mPauseButton.requestFocus();
mPauseButton.setOnClickListener(mPauseListener);
}
mFfwdButton = (ImageButton) v.findViewById(R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
if (!mFromXml) {
mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
}
}
mRewButton = (ImageButton) v.findViewById(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(R.id.next);
if (mNextButton != null && !mFromXml && !mListenersSet) {
mNextButton.setVisibility(View.GONE);
}
mPrevButton = (ImageButton) v.findViewById(R.id.prev);
if (mPrevButton != null && !mFromXml && !mListenersSet) {
mPrevButton.setVisibility(View.GONE);
}
mProgress = (ProgressBar) v.findViewById(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(R.id.time);
mCurrentTime = (TextView) v.findViewById(R.id.time_current);
mFormatBuilder = new StringBuilder();
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
mFullScreenButton = (ImageButton) v.findViewById(R.id.video_fullscreen);
installFullScreenListener();
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.
*/
public void show(int timeout) {
if (!mShowing && mAnchor != null) {
setProgress();
addToFrameAncherLayout();
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);
}
}
private void addToFrameAncherLayout() {
if ((mDecor != null) && (mDecor.getParent() == null)) {
if (mAnchor instanceof FrameLayout) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM;
((FrameLayout) mAnchor).addView(mDecor, params);
mDecor.setVisibility(View.VISIBLE);
} else {
// 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 = anchorpos[0];
// p.y = anchorpos[1] + mAnchor.getHeight() -
// mDecor.getHeight();
// 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);
// Logger.e("Ancher is not a Frame Layout usinf a Blocking Window");
}
}
mDecor.setVisibility(View.VISIBLE);
}
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);
// TODO must be a New View
if (mDecor != null) {
if (mAnchor instanceof FrameLayout) {
// ((FrameLayout) mAnchor).removeView(mDecor);
mDecor.setVisibility(View.GONE);
} else {
// mWindowManager.removeView(mRoot);
}
}
} catch (IllegalArgumentException ex) {
Log.w("MediaController", "already removed");
}
mShowing = false;
}
}
static class ProgressHandler extends Handler {
WeakReference<CustomMediaController> mediaControllerReference;
ProgressHandler(CustomMediaController mediaController) {
mediaControllerReference = new WeakReference<CustomMediaController>(mediaController);
}
#Override
public void handleMessage(Message msg) {
CustomMediaController mediaController = mediaControllerReference.get();
if (mediaController != null) {
int pos;
switch (msg.what) {
case FADE_OUT:
mediaController.hide();
break;
case SHOW_PROGRESS:
pos = mediaController.setProgress();
if (!mediaController.mDragging && mediaController.mShowing
&& mediaController.mPlayer.isPlaying()) {
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, 1000 - (pos % 1000));
}
break;
}
}
}
}
private final Handler mHandler = new ProgressHandler(this);
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
&& (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 final View.OnClickListener mPauseListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
doPauseResume();
show(sDefaultTimeout);
}
};
private void updatePausePlay() {
if (mRoot == null)
return;
ImageButton button = (ImageButton) mRoot.findViewById(R.id.pause);
if (button == null)
return;
if (mPlayer.isPlaying()) {
button.setImageResource(R.drawable.ic_media_pause);
} else {
button.setImageResource(R.drawable.ic_media_play);
}
}
private void doPauseResume() {
if (mPlayer.isPlaying()) {
mPlayer.pause();
} else {
mPlayer.start();
}
updatePausePlay();
}
private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
long duration;
#Override
public void onStartTrackingTouch(SeekBar bar) {
show(3600000);
duration = mPlayer.getDuration();
}
#Override
public void onProgressChanged(SeekBar bar, int progress,
boolean fromtouch) {
if (fromtouch) {
mDragging = true;
duration = mPlayer.getDuration();
long newposition = (duration * progress) / 1000L;
mPlayer.seekTo((int) newposition);
if (mCurrentTime != null)
mCurrentTime
.setText(stringForTime((int) newposition));
} else {
if ((fullScreenListener != null) && (progress > 0)) {
fullScreenListener.onPlaying();
}
}
}
#Override
public void onStopTrackingTouch(SeekBar bar) {
mDragging = false;
setProgress();
updatePausePlay();
show(sDefaultTimeout);
}
};
#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 final View.OnClickListener mRewListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
int pos = mPlayer.getCurrentPosition();
pos -= 5000; // milliseconds
mPlayer.seekTo(pos);
setProgress();
show(sDefaultTimeout);
}
};
private final View.OnClickListener mFfwdListener = new View.OnClickListener() {
#Override
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);
}
}
private void installFullScreenListener() {
if (mFullScreenButton != null) {
mFullScreenButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View aView) {
Preconditions.checkNotNull(fullScreenListener,
"IllegalStateExeption, FullScreen Button pressed with a nullable Listener");
if (isFullSreen) {
// send the request to handle normal size;
fullScreenListener.onStreatchScreenRequest();
isFullSreen = false;
updateFullScreenButton();
} else {
// Send request to go FullScreen
fullScreenListener.onFullScreenRequest();
isFullSreen = true;
updateFullScreenButton();
}
}
});
if (fullScreenListener == null) {
mFullScreenButton.setVisibility(View.GONE);
} else {
mFullScreenButton.setVisibility(View.VISIBLE);
}
}
}
/**
* Toggle the Full Screen Buttons
*/
private void updateFullScreenButton() {
if (isFullSreen) {
mFullScreenButton.setImageResource(R.drawable.windowmin);
} else {
mFullScreenButton.setImageResource(R.drawable.windowmax);
}
}
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 MediaPlayerControl getmPlayer() {
return mPlayer;
}
public void setmPlayer(MediaPlayerControl mPlayer) {
this.mPlayer = mPlayer;
}
#Override
public void dispatchWindowVisibilityChanged(int visibility) {
hide();
}
/**
* #return the fullScreenListener or null if any Listener is registred.
*/
public FullScreenMediaPlayer getFullScreenListener() {
return fullScreenListener;
}
/**
* #param aFullScreenListener
* the fullScreenListener.
*/
public void registerFullScreenListener(FullScreenMediaPlayer aFullScreenListener) {
Preconditions.checkNotNull(aFullScreenListener, "FullScreenListener cannot be null ");
fullScreenListener = aFullScreenListener;
installFullScreenListener();
}
public void removeFullScreenListerer() {
fullScreenListener = null;
installFullScreenListener();
}
/*
* (non-Javadoc)
*
* #see android.view.View#onDetachedFromWindow()
*/
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
hide();
}
}
Related
I am getting null pointer on back press of activity where I have used the custom view. Where am I doing wrong?? Below is my custom view class:
public class PuzzleBoardView extends View implements ViewTreeObserver.OnGlobalLayoutListener {
public static final int NUM_SHUFFLE_STEPS = 40;
Comparator<PuzzleBoard> comparator = new PuzzleBoardComparator();
PriorityQueue<PuzzleBoard> queue = new PriorityQueue<>(9999, comparator);
private PuzzleActivity activity;
private PuzzleBoard puzzleBoard;
private ArrayList<PuzzleBoard> animation;
private Random random = new Random();
private Bitmap imgBitmap;
public PuzzleBoardView(Context context) {
super(context);
setMinimumWidth(50);
activity = (PuzzleActivity) context;
animation = null;
}
public void initialize(final Bitmap imageBitmap, Activity parent) {
final int width = getWidth();
imgBitmap = imageBitmap;
Log.d("PuzzleBoardView", "initialize: width=== " + width);
addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
#Override
public void onViewAttachedToWindow(View v) {
Log.d("PuzzleBoardView", "onViewAttachedToWindow=== " + width);
puzzleBoard = new PuzzleBoard(imgBitmap, getWidth());
shuffle();
}
#Override
public void onViewDetachedFromWindow(View v) {
Log.d("PuzzleBoardView", "onViewDetachedFromWindow=== ");
onDetachedFromWindow();
}
});
}
public void clearView() {
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("TAG", "onDraw: =====");
if (puzzleBoard != null) {
Log.d("TAG", "onDraw:111 =====");
if (animation != null && animation.size() > 0) {
puzzleBoard = animation.remove(0);
puzzleBoard.draw(canvas);
if (animation.size() == 0) {
animation = null;
puzzleBoard.reset();
Toast toast = Toast.makeText(activity, "Solved! ", Toast.LENGTH_LONG);
toast.show();
} else {
this.postInvalidateDelayed(500);
}
} else {
puzzleBoard.draw(canvas);
}
}
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.d("PuzzleBoardView", "onDetachedFromWindow=== ");
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
public void shuffle() {
if (animation == null && puzzleBoard != null) {
// Do something. Then:
ArrayList<PuzzleBoard> boards;
for (int i = 0; i <= NUM_SHUFFLE_STEPS; i++) {
boards = puzzleBoard.neighbours();
puzzleBoard = boards.get(random.nextInt(boards.size()));
}
puzzleBoard.reset();
invalidate();
queue.clear();
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (animation == null && puzzleBoard != null) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (puzzleBoard.click(event.getX(), event.getY())) {
invalidate();
if (puzzleBoard.resolved()) {
/*Toast toast = Toast.makeText(activity, "Congratulations!", Toast.LENGTH_LONG);
toast.show();*/
activity.puzzleSolved();
}
return true;
}
}
}
return super.onTouchEvent(event);
}
public void solve() {
puzzleBoard.steps = 0;
puzzleBoard.previousBoard = null;
queue.add(puzzleBoard);
PuzzleBoard prev = null;
ArrayList<PuzzleBoard> solution = new ArrayList<>();
while (!queue.isEmpty()) {
PuzzleBoard lowest = queue.poll();
if (lowest.priority() - lowest.steps != 0) {
for (PuzzleBoard toAdd : lowest.neighbours()) {
if (!toAdd.equals(prev)) {
queue.add(toAdd);
}
}
prev = lowest;
} else {
solution.add(lowest);
while (lowest != null) {
if (lowest.getPreviousBoard() == null) {
break;
}
solution.add(lowest.getPreviousBoard());
lowest = lowest.getPreviousBoard();
}
Collections.reverse(solution);
animation = solution;
invalidate();
break;
}
}
}
public PuzzleBoard getPuzzleBoard() {
return puzzleBoard;
}
public void setPuzzleBoard(PuzzleBoard puzzleBoard) {
this.puzzleBoard = puzzleBoard;
}
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
Log.d("PuzzleBoardView", "onAttachedToWindow: width=== " + getWidth());
puzzleBoard = new PuzzleBoard(imgBitmap, getWidth());
shuffle();
}
}
class PuzzleBoardComparator implements Comparator<PuzzleBoard> {
#Override
public int compare(PuzzleBoard first, PuzzleBoard second) {
if (first.priority() == second.priority()) {
return 0;
} else if (first.priority() < second.priority()) {
return -1;
} else {
return 1;
}
}
}
Here is my Exception
java.lang.NullPointerException: Attempt to invoke interface method 'void android.view.View$OnAttachStateChangeListener.onViewDetachedFromWindow(android.view.View)' on a null object reference
Change your code like below.
public PuzzleBoardView(Context context) {
super(context);
setMinimumWidth(50);
activity = (PuzzleActivity) context;
animation = null;
if(activity!=null)
initialize();
}
and remove initialize method from onCreate()
I am using Vitamio for Android (latest version, 5.2.3, can be found here
). Vitamio uses a slightly modified version of the original Android MediaController-class and my problem is that the mediacontroller(and seekbar) does not show up when tapping on the screen/surfaceview while video is playing. I get this error:
01-04 18:07:54.198 1626-1969/system_process I/WindowManager: Destroying surface Surface(name=PopupWindow:7a167d2) called by com.android.server.wm.WindowStateAnimator.destroySurface:2014 com.android.server.wm.WindowStateAnimator.destroySurfaceLocked:881 com.android.server.wm.WindowState.removeLocked:1449 com.android.server.wm.WindowManagerService.removeWindowInnerLocked:2478 com.android.server.wm.WindowManagerService.removeWindowLocked:2436 com.android.server.wm.WindowManagerService.removeWindowLocked:2305 com.android.server.wm.WindowManagerService.removeWindow:2300 com.android.server.wm.Session.remove:193
However, it only happens with SDK 24 while earlier and successive SDK versions are fine.
I tried to modify the MediaController-class to use Callbacks instead of Handlers (as the original MediaController Android source) but the result is the same - still not working with SDK 24.
Anybody who knows what the problem can be? I have a suspicion it might be related to the changes mentioned here but I don't fully understand what I need to do.
Any help would be most appreciated.
Here below is my modified MediaController-class:
package io.vov.vitamio.widget;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.media.AudioManager;
import android.os.Build;
//import android.os.Message;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.PopupWindow;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import java.lang.reflect.Method;
import io.vov.vitamio.utils.Log;
import io.vov.vitamio.utils.StringUtils;
//import android.os.Message;
//import android.os.Handler;
//import android.os.Message;
/**
* A view containing controls for a MediaPlayer. Typically contains the buttons
* like "Play/Pause" 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 a) instantiate it programatically or b)
* create it in your xml layout.
* <p/>
* a) 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(). By default, the window
* will disappear if left idle for three seconds and reappear when the user
* touches the anchor view. To customize the MediaController's style, layout and
* controls you should extend MediaController and override the {#link
* {#link #makeControllerView()} method.
* <p/>
* b) The MediaController is a FrameLayout, you can put it in your layout xml
* and get it through {#link #findViewById(int)}.
* <p/>
* NOTES: In each way, if you want customize the MediaController, the SeekBar's
* id must be mediacontroller_progress, the Play/Pause's must be
* mediacontroller_pause, current time's must be mediacontroller_time_current,
* total time's must be mediacontroller_time_total, file name's must be
* mediacontroller_file_name. And your resources must have a pause_button
* drawable and a play_button drawable.
* <p/>
* Functions like show() and hide() have no effect when MediaController is
* created in an xml layout.
*/
public class MediaController extends FrameLayout {
private static final int sDefaultTimeout = 3000;
private MediaPlayerControl mPlayer;
private Context mContext;
private PopupWindow mWindow;
private int mAnimStyle;
private View mAnchor;
private View mRoot;
private SeekBar mProgress;
private TextView mEndTime, mCurrentTime;
private TextView mFileName;
private OutlineTextView mInfoView;
private String mTitle;
private long mDuration;
private boolean mShowing;
private boolean mDragging;
private boolean mInstantSeeking = false;
private boolean mFromXml = false;
private ImageButton mPauseButton;
private AudioManager mAM;
private OnShownListener mShownListener;
private OnHiddenListener mHiddenListener;
#SuppressLint("HandlerLeak")
private View.OnClickListener mPauseListener = new View.OnClickListener() {
public void onClick(View v) {
doPauseResume();
show(sDefaultTimeout);
}
};
private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
public void onStartTrackingTouch(SeekBar bar) {
mDragging = true;
show(3600000);
removeCallbacks(mShowProgress);
if (mInstantSeeking)
mAM.setStreamMute(AudioManager.STREAM_MUSIC, true);
if (mInfoView != null) {
mInfoView.setText("");
mInfoView.setVisibility(View.VISIBLE);
}
}
public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
if (!fromuser)
return;
long newposition = (mDuration * progress) / 1000;
String time = StringUtils.generateTime(newposition);
if (mInstantSeeking)
mPlayer.seekTo(newposition);
if (mInfoView != null)
mInfoView.setText(time);
if (mCurrentTime != null)
mCurrentTime.setText(time);
}
public void onStopTrackingTouch(SeekBar bar) {
if (!mInstantSeeking)
mPlayer.seekTo((mDuration * bar.getProgress()) / 1000);
if (mInfoView != null) {
mInfoView.setText("");
mInfoView.setVisibility(View.GONE);
}
show(sDefaultTimeout);
// mHandler.removeMessages(SHOW_PROGRESS);
removeCallbacks(mShowProgress);
mAM.setStreamMute(AudioManager.STREAM_MUSIC, false);
mDragging = false;
// mHandler.sendEmptyMessageDelayed(SHOW_PROGRESS, 1000);
post(mShowProgress);
}
};
public MediaController(Context context, AttributeSet attrs) {
super(context, attrs);
mRoot = this;
mFromXml = true;
initController(context);
}
public MediaController(Context context) {
super(context);
if (!mFromXml && initController(context))
initFloatingWindow();
}
private boolean initController(Context context) {
mContext = context;
mAM = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return true;
}
#Override
public void onFinishInflate() {
if (mRoot != null)
initControllerView(mRoot);
}
private void initFloatingWindow() {
mWindow = new PopupWindow(mContext);
mWindow.setFocusable(false);
mWindow.setBackgroundDrawable(null);
mWindow.setOutsideTouchable(true);
mAnimStyle = android.R.style.Animation;
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void setWindowLayoutType() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
Method setWindowLayoutType = PopupWindow.class.getMethod("setWindowLayoutType", new Class[] { int.class });
setWindowLayoutType.invoke(mWindow, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG);
} catch (Exception e) {
Log.e("setWindowLayoutType", e);
}
}
}
/**
* 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;
if (!mFromXml) {
removeAllViews();
mRoot = makeControllerView();
mWindow.setContentView(mRoot);
mWindow.setWidth(LayoutParams.MATCH_PARENT);
mWindow.setHeight(LayoutParams.WRAP_CONTENT);
}
initControllerView(mRoot);
}
/**
* Create the view that holds the widgets that control playback. Derived
* classes can override this to create their own.
*
* #return The controller view.
*/
protected View makeControllerView() {
return ((LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("mediacontroller", "layout", mContext.getPackageName()), this);
}
private void initControllerView(View v) {
mPauseButton = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_play_pause", "id", mContext.getPackageName()));
if (mPauseButton != null) {
mPauseButton.requestFocus();
mPauseButton.setOnClickListener(mPauseListener);
}
mProgress = (SeekBar) v.findViewById(getResources().getIdentifier("mediacontroller_seekbar", "id", mContext.getPackageName()));
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
seeker.setOnSeekBarChangeListener(mSeekListener);
}
mProgress.setMax(1000);
}
mEndTime = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_time_total", "id", mContext.getPackageName()));
mCurrentTime = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_time_current", "id", mContext.getPackageName()));
mFileName = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_file_name", "id", mContext.getPackageName()));
if (mFileName != null)
mFileName.setText(mTitle);
}
public void setMediaPlayer(MediaPlayerControl player) {
mPlayer = player;
updatePausePlay();
}
/**
* Control the action when the seekbar dragged by user
*
* #param seekWhenDragging True the media will seek periodically
*/
public void setInstantSeeking(boolean seekWhenDragging) {
mInstantSeeking = seekWhenDragging;
}
public void show() {
show(sDefaultTimeout);
}
/**
* Set the content of the file_name TextView
*
* #param name
*/
public void setFileName(String name) {
mTitle = name;
if (mFileName != null)
mFileName.setText(mTitle);
}
/**
* Set the View to hold some information when interact with the
* MediaController
*
* #param v
*/
public void setInfoView(OutlineTextView v) {
mInfoView = v;
}
/**
* <p>
* Change the animation style resource for this controller.
* </p>
* <p/>
* <p>
* If the controller is showing, calling this method will take effect only the
* next time the controller is shown.
* </p>
*
* #param animationStyle animation style to use when the controller appears
* and disappears. Set to -1 for the default animation, 0 for no animation, or
* a resource identifier for an explicit animation.
*/
public void setAnimationStyle(int animationStyle) {
mAnimStyle = animationStyle;
}
/**
* 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.
*/
public void show(int timeout) {
if (!mShowing && mAnchor != null && mAnchor.getWindowToken() != null) {
if (mPauseButton != null)
mPauseButton.requestFocus();
if (mFromXml) {
setVisibility(View.VISIBLE);
} else {
int[] location = new int[2];
mAnchor.getLocationOnScreen(location);
Rect anchorRect = new Rect(location[0], location[1], location[0] + mAnchor.getWidth(), location[1] + mAnchor.getHeight());
mWindow.setAnimationStyle(mAnimStyle);
setWindowLayoutType();
mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, anchorRect.left, anchorRect.bottom);
}
mShowing = true;
if (mShownListener != null)
mShownListener.onShown();
}
updatePausePlay();
// mHandler.sendEmptyMessage(SHOW_PROGRESS);
post(mShowProgress); // DA VEDERE
if (timeout != 0) {
// mHandler.removeMessages(FADE_OUT);
// mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT), timeout);
removeCallbacks(mFadeOut); //DA VEDERE
postDelayed(mFadeOut, timeout); //DA VEDERE
}
}
public boolean isShowing() {
return mShowing;
}
public void hide() {
if (mAnchor == null)
return;
if (mShowing) {
try {
// mHandler.removeMessages(SHOW_PROGRESS);
removeCallbacks(mShowProgress); //DA VEDERE
if (mFromXml)
setVisibility(View.GONE);
else
mWindow.dismiss();
} catch (IllegalArgumentException ex) {
Log.d("MediaController already removed");
}
mShowing = false;
if (mHiddenListener != null)
mHiddenListener.onHidden();
}
}
private final Runnable mFadeOut = new Runnable() {
#Override
public void run() {
hide();
}
};
private final Runnable mShowProgress = new Runnable() {
#Override
public void run() {
long pos = setProgress();
if (!mDragging && mShowing && mPlayer.isPlaying()) {
postDelayed(mShowProgress, 1000 - (pos % 1000));
}
}
};
public void setOnShownListener(OnShownListener l) {
mShownListener = l;
}
public void setOnHiddenListener(OnHiddenListener l) {
mHiddenListener = l;
}
private long setProgress() {
if (mPlayer == null || mDragging)
return 0;
long position = mPlayer.getCurrentPosition();
long duration = mPlayer.getDuration();
if (mProgress != null) {
if (duration > 0) {
long pos = 1000L * position / duration;
mProgress.setProgress((int) pos);
}
int percent = mPlayer.getBufferPercentage();
mProgress.setSecondaryProgress(percent * 10);
}
mDuration = duration;
if (mEndTime != null)
mEndTime.setText(StringUtils.generateTime(mDuration));
if (mCurrentTime != null)
mCurrentTime.setText(StringUtils.generateTime(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 && (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE)) {
doPauseResume();
show(sDefaultTimeout);
if (mPauseButton != null)
mPauseButton.requestFocus();
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) {
if (mPlayer.isPlaying()) {
mPlayer.pause();
updatePausePlay();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
hide();
return true;
} else {
show(sDefaultTimeout);
}
return super.dispatchKeyEvent(event);
}
private void updatePausePlay() {
if (mRoot == null || mPauseButton == null)
return;
if (mPlayer.isPlaying())
mPauseButton.setImageResource(getResources().getIdentifier("mediacontroller_pause", "drawable", mContext.getPackageName()));
else
mPauseButton.setImageResource(getResources().getIdentifier("mediacontroller_play", "drawable", mContext.getPackageName()));
}
private void doPauseResume() {
if (mPlayer.isPlaying())
mPlayer.pause();
else
mPlayer.start();
updatePausePlay();
}
#Override
public void setEnabled(boolean enabled) {
if (mPauseButton != null)
mPauseButton.setEnabled(enabled);
if (mProgress != null)
mProgress.setEnabled(enabled);
super.setEnabled(enabled);
}
public interface OnShownListener {
public void onShown();
}
public interface OnHiddenListener {
public void onHidden();
}
public interface MediaPlayerControl {
void start();
void pause();
long getDuration();
long getCurrentPosition();
void seekTo(long pos);
boolean isPlaying();
int getBufferPercentage();
}
}
Turns out it had nothing to do with SurfaceView, Callbacks, Handlers etc.
It was related to Android's PopupWindow which was not working in the same way as in previous versions. There are some posts about this here in the forum, some calling it a bug.
For those Vitamio users who might hit this problem and come here to SO, I resolved by modifying the Show(int timeout) method as follows:
if (Build.VERSION.SDK_INT == 24) {
mWindow.showAsDropDown(mAnchor);
} else {
mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, anchorRect.left, anchorRect.bottom);
}
I am using Vitamio Library in my app. But the problem is, while playing when i try to move seek bar forward or backward the progress bar is in accurate.
I debug the code and found that, in setProgress() method in MediaController class:
long position = mPlayer.getCurrentPosition();
this position is always same. I am not getting that why getCurrentPosition() is always returning same value.
I also search it on Vitamio website and i found:
https://www.vitamio.org/en/docs/FAQ/2013/0509/6.html
When I drag the seekBar, Why the progress bar is inaccurate?
It's all right, this isn't Vitamio's bug, because you must drag the seekBar to the key frame, But the key frame is not always on every timestamp, except you use the intra-only encoding.
What does this mean? and how can i fix it. Please help me.
In Vitamio When I drag the seekBar, the progress bar is inaccurate? How to fix it?
I just wanted to tell you all that I had made a custom solution to fix this issue. You can take help from this solution If you still facing this issue. I have made changes in MediaController.java class in library. You just need to replace this class from your previous class and then run your code.
package io.vov.vitamio.widget;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.PopupWindow;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import java.lang.reflect.Method;
import io.vov.vitamio.utils.Log;
import io.vov.vitamio.utils.StringUtils;
public class MediaController extends FrameLayout {
private static final int sDefaultTimeout = 3000;
private static final int FADE_OUT = 1;
private static final int SHOW_PROGRESS = 2;
private MediaPlayerControl mPlayer;
private Context mContext;
private PopupWindow mWindow;
private int mAnimStyle;
private View mAnchor;
private View mRoot;
private SeekBar mProgress;
private TextView mEndTime, mCurrentTime;
private TextView mFileName;
private OutlineTextView mInfoView;
private String mTitle;
private long mDuration;
private boolean mShowing;
private boolean mDragging;
private boolean mInstantSeeking = false;
private boolean mFromXml = false;
private ImageButton mPauseButton;
private AudioManager mAM;
private OnShownListener mShownListener;
private OnHiddenListener mHiddenListener;
private boolean isSeekBar = false; // Change for video time stuck
private boolean isPlayButtonVisible = false; // Change for video time stuck
#SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
long pos;
switch (msg.what) {
case FADE_OUT:
hide();
break;
case SHOW_PROGRESS:
if(!isSeekBar){ // Change for video time stuck
pos = setProgress();
if (!mDragging && mShowing) {
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, 1000 - (pos % 1000));
// updatePausePlay(); // Change for video time stuck
}
}
break;
}
}
};
private View.OnClickListener mPauseListener = new View.OnClickListener() {
public void onClick(View v) {
doPauseResume();
show(sDefaultTimeout);
}
};
private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
public void onStartTrackingTouch(SeekBar bar) {
if(mPlayer.isPlaying()){ // // Change for video time stuck
isSeekBar = true; // // Change for video time stuck
mDragging = true;
show(3600000);
mHandler.removeMessages(SHOW_PROGRESS);
if (mInstantSeeking)
mAM.setStreamMute(AudioManager.STREAM_MUSIC, true);
if (mInfoView != null) {
mInfoView.setText("");
mInfoView.setVisibility(View.VISIBLE);
}
}
}
public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
if (!fromuser)
return;
if(mPlayer.isPlaying()){ // // Change for video time stuck
long newposition = (mDuration * progress) / 1000;
String time = StringUtils.generateTime(newposition);
if (mInstantSeeking)
mPlayer.seekTo(newposition);
if (mInfoView != null)
mInfoView.setText(time);
if (mCurrentTime != null)
mCurrentTime.setText(time);
}
}
public void onStopTrackingTouch(SeekBar bar) {
isSeekBar = false; // // Change for video time stuck
if(mPlayer.isPlaying()){ // // Change for video time stuck
if (!mInstantSeeking)
mPlayer.seekTo((mDuration * bar.getProgress()) / 1000);
if (mInfoView != null) {
mInfoView.setText("");
mInfoView.setVisibility(View.GONE);
}
show(sDefaultTimeout);
mHandler.removeMessages(SHOW_PROGRESS);
mAM.setStreamMute(AudioManager.STREAM_MUSIC, false);
mDragging = false;
mHandler.sendEmptyMessageDelayed(SHOW_PROGRESS, 1000);
}
}
};
public MediaController(Context context, AttributeSet attrs) {
super(context, attrs);
mRoot = this;
mFromXml = true;
initController(context);
}
public MediaController(Context context) {
super(context);
if (!mFromXml && initController(context))
initFloatingWindow();
}
private boolean initController(Context context) {
mContext = context;
mAM = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return true;
}
#Override
public void onFinishInflate() {
if (mRoot != null)
initControllerView(mRoot);
}
private void initFloatingWindow() {
mWindow = new PopupWindow(mContext);
mWindow.setFocusable(false);
mWindow.setBackgroundDrawable(null);
mWindow.setOutsideTouchable(true);
mAnimStyle = android.R.style.Animation;
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void setWindowLayoutType() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
try {
mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
Method setWindowLayoutType = PopupWindow.class.getMethod("setWindowLayoutType", new Class[] { int.class });
setWindowLayoutType.invoke(mWindow, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG);
} catch (Exception e) {
Log.e("setWindowLayoutType", e);
}
}
}
/**
* 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;
if (!mFromXml) {
removeAllViews();
mRoot = makeControllerView();
mWindow.setContentView(mRoot);
mWindow.setWidth(LayoutParams.MATCH_PARENT);
mWindow.setHeight(LayoutParams.WRAP_CONTENT);
}
initControllerView(mRoot);
}
/**
* Create the view that holds the widgets that control playback. Derived
* classes can override this to create their own.
*
* #return The controller view.
*/
protected View makeControllerView() {
return ((LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("mediacontroller", "layout", mContext.getPackageName()), this);
}
private void initControllerView(View v) {
mPauseButton = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_play_pause", "id", mContext.getPackageName()));
if (mPauseButton != null) {
mPauseButton.requestFocus();
mPauseButton.setOnClickListener(mPauseListener);
}
mProgress = (SeekBar) v.findViewById(getResources().getIdentifier("mediacontroller_seekbar", "id", mContext.getPackageName()));
if (mProgress != null) {
if (mProgress instanceof SeekBar) {
SeekBar seeker = (SeekBar) mProgress;
seeker.setOnSeekBarChangeListener(mSeekListener);
}
mProgress.setMax(1000);
}
mEndTime = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_time_total", "id", mContext.getPackageName()));
mCurrentTime = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_time_current", "id", mContext.getPackageName()));
mFileName = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_file_name", "id", mContext.getPackageName()));
if (mFileName != null)
mFileName.setText(mTitle);
}
public void setMediaPlayer(MediaPlayerControl player) {
mPlayer = player;
updatePausePlay();
}
/**
* Control the action when the seekbar dragged by user
*
* #param seekWhenDragging True the media will seek periodically
*/
public void setInstantSeeking(boolean seekWhenDragging) {
mInstantSeeking = seekWhenDragging;
}
public void show() {
show(sDefaultTimeout);
}
/**
* Set the content of the file_name TextView
*
* #param name
*/
public void setFileName(String name) {
mTitle = name;
if (mFileName != null)
mFileName.setText(mTitle);
}
/**
* Set the View to hold some information when interact with the
* MediaController
*
* #param v
*/
public void setInfoView(OutlineTextView v) {
mInfoView = v;
}
/**
* <p>
* Change the animation style resource for this controller.
* </p>
* <p/>
* <p>
* If the controller is showing, calling this method will take effect only the
* next time the controller is shown.
* </p>
*
* #param animationStyle animation style to use when the controller appears
* and disappears. Set to -1 for the default animation, 0 for no animation, or
* a resource identifier for an explicit animation.
*/
public void setAnimationStyle(int animationStyle) {
mAnimStyle = animationStyle;
}
/**
* 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.
*/
public void show(int timeout) {
if (!mShowing && mAnchor != null && mAnchor.getWindowToken() != null) {
if (mPauseButton != null)
mPauseButton.requestFocus();
if (mFromXml) {
setVisibility(View.VISIBLE);
} else {
int[] location = new int[2];
mAnchor.getLocationOnScreen(location);
Rect anchorRect = new Rect(location[0], location[1], location[0] + mAnchor.getWidth(), location[1] + mAnchor.getHeight());
mWindow.setAnimationStyle(mAnimStyle);
setWindowLayoutType();
mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, anchorRect.left, anchorRect.bottom);
}
mShowing = true;
if (mShownListener != null)
mShownListener.onShown();
}
// updatePausePlay(); // Change for video time stuck
mHandler.sendEmptyMessage(SHOW_PROGRESS);
if (timeout != 0) {
mHandler.removeMessages(FADE_OUT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT), timeout);
}
}
public boolean isShowing() {
return mShowing;
}
public void hide() {
if (mAnchor == null)
return;
if (mShowing) {
try {
mHandler.removeMessages(SHOW_PROGRESS);
if (mFromXml)
setVisibility(View.GONE);
else
mWindow.dismiss();
} catch (IllegalArgumentException ex) {
Log.d("MediaController already removed");
}
mShowing = false;
if (mHiddenListener != null)
mHiddenListener.onHidden();
}
}
public void setOnShownListener(OnShownListener l) {
mShownListener = l;
}
public void setOnHiddenListener(OnHiddenListener l) {
mHiddenListener = l;
}
private long setProgress() {
if (mPlayer == null || mDragging)
return 0;
long position = mPlayer.getCurrentPosition();
long duration = mPlayer.getDuration();
if (mProgress != null) {
if (duration > 0) {
long pos = 1000L * position / duration;
mProgress.setProgress((int) pos);
}
int percent = mPlayer.getBufferPercentage();
mProgress.setSecondaryProgress(percent * 10);
}
mDuration = duration;
if (mEndTime != null)
mEndTime.setText(StringUtils.generateTime(mDuration));
if (mCurrentTime != null)
mCurrentTime.setText(StringUtils.generateTime(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 && (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE)) {
doPauseResume();
show(sDefaultTimeout);
if (mPauseButton != null)
mPauseButton.requestFocus();
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) {
if (mPlayer.isPlaying()) {
mPlayer.pause();
updatePausePlay();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
hide();
return true;
} else {
show(sDefaultTimeout);
}
return super.dispatchKeyEvent(event);
}
private void updatePausePlay() {
if (mRoot == null || mPauseButton == null)
return;
if (mPlayer.isPlaying()){
isPlayButtonVisible = false; // // Change for video time stuck
mPauseButton.setImageResource(getResources().getIdentifier("mediacontroller_pause", "drawable", mContext.getPackageName()));
}else{
isPlayButtonVisible = true; // // Change for video time stuck
mPauseButton.setImageResource(getResources().getIdentifier("mediacontroller_play", "drawable", mContext.getPackageName()));
}
}
private void doPauseResume() {
if (mPlayer.isPlaying())
mPlayer.pause();
else
mPlayer.start();
updatePausePlay();
}
#Override
public void setEnabled(boolean enabled) {
if (mPauseButton != null)
mPauseButton.setEnabled(enabled);
if (mProgress != null)
mProgress.setEnabled(enabled);
super.setEnabled(enabled);
}
public interface OnShownListener {
public void onShown();
}
public interface OnHiddenListener {
public void onHidden();
}
public interface MediaPlayerControl {
void start();
void pause();
long getDuration();
long getCurrentPosition();
void seekTo(long pos);
boolean isPlaying();
int getBufferPercentage();
}
}
i am using the following code for custom media controller. the code is working fine but if i try to toggle full screen in portrait mode the video is stretched. if i give the fixed height and width the video is not stretched what is the proper way of implementing the toggle screen button in media controller.
Video Controller:
package your.package.name;
import android.content.Context;
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.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.Formatter;
import java.util.Locale;
import your.resource.path;
/**
* 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 VideoControllerView extends FrameLayout {
private static final String TAG = "VideoControllerView";
private MediaPlayerControl mPlayer;
private Context mContext;
private ViewGroup mAnchor;
private View mRoot;
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;
private ImageButton mFullscreenButton;
private Handler mHandler = new MessageHandler(this);
public VideoControllerView(Context context, AttributeSet attrs) {
super(context, attrs);
mRoot = null;
mContext = context;
mUseFastForward = true;
mFromXml = true;
Log.i(TAG, TAG);
}
public VideoControllerView(Context context, boolean useFastForward) {
super(context);
mContext = context;
mUseFastForward = useFastForward;
Log.i(TAG, TAG);
}
public VideoControllerView(Context context) {
this(context, true);
Log.i(TAG, TAG);
}
#Override
public void onFinishInflate() {
if (mRoot != null)
initControllerView(mRoot);
}
public void setMediaPlayer(MediaPlayerControl player) {
mPlayer = player;
updatePausePlay();
updateFullScreen();
}
/**
* 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(ViewGroup view) {
mAnchor = view;
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_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(R.layout.media_controller, null);
initControllerView(mRoot);
return mRoot;
}
private void initControllerView(View v) {
mPauseButton = (ImageButton) v.findViewById(R.id.pause);
if (mPauseButton != null) {
mPauseButton.requestFocus();
mPauseButton.setOnClickListener(mPauseListener);
}
mFullscreenButton = (ImageButton) v.findViewById(R.id.fullscreen);
if (mFullscreenButton != null) {
mFullscreenButton.requestFocus();
mFullscreenButton.setOnClickListener(mFullscreenListener);
}
mFfwdButton = (ImageButton) v.findViewById(R.id.ffwd);
if (mFfwdButton != null) {
mFfwdButton.setOnClickListener(mFfwdListener);
if (!mFromXml) {
mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
}
}
mRewButton = (ImageButton) v.findViewById(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(R.id.next);
if (mNextButton != null && !mFromXml && !mListenersSet) {
mNextButton.setVisibility(View.GONE);
}
mPrevButton = (ImageButton) v.findViewById(R.id.prev);
if (mPrevButton != null && !mFromXml && !mListenersSet) {
mPrevButton.setVisibility(View.GONE);
}
mProgress = (ProgressBar) v.findViewById(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(R.id.time);
mCurrentTime = (TextView) v.findViewById(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);
}
/**
* Disable pause or seek buttons if the stream cannot be paused or seeked.
* This requires the control interface to be a MediaPlayerControlExt
*/
private void disableUnsupportedButtons() {
if (mPlayer == null) {
return;
}
try {
if (mPauseButton != null && !mPlayer.canPause()) {
mPauseButton.setEnabled(false);
}
if (mRewButton != null && !mPlayer.canSeekBackward()) {
mRewButton.setEnabled(false);
}
if (mFfwdButton != null && !mPlayer.canSeekForward()) {
mFfwdButton.setEnabled(false);
}
} catch (IncompatibleClassChangeError ex) {
// We were given an old version of the interface, that doesn't have
// the canPause/canSeekXYZ methods. This is OK, it just means we
// assume the media can be paused and seeked, and so we don't disable
// the buttons.
}
}
/**
* 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.
*/
public void show(int timeout) {
if (!mShowing && mAnchor != null) {
setProgress();
if (mPauseButton != null) {
mPauseButton.requestFocus();
}
disableUnsupportedButtons();
FrameLayout.LayoutParams tlp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM
);
mAnchor.addView(this, tlp);
mShowing = true;
}
updatePausePlay();
updateFullScreen();
// 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;
}
try {
mAnchor.removeView(this);
mHandler.removeMessages(SHOW_PROGRESS);
} catch (IllegalArgumentException ex) {
Log.w("MediaController", "already removed");
}
mShowing = false;
}
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) {
if (mPlayer == null) {
return true;
}
int keyCode = event.getKeyCode();
final boolean uniqueDown = event.getRepeatCount() == 0
&& event.getAction() == KeyEvent.ACTION_DOWN;
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keyCode == KeyEvent.KEYCODE_SPACE) {
if (uniqueDown) {
doPauseResume();
show(sDefaultTimeout);
if (mPauseButton != null) {
mPauseButton.requestFocus();
}
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
if (uniqueDown && !mPlayer.isPlaying()) {
mPlayer.start();
updatePausePlay();
show(sDefaultTimeout);
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
if (uniqueDown && mPlayer.isPlaying()) {
mPlayer.pause();
updatePausePlay();
show(sDefaultTimeout);
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP
|| keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
// don't show the controls for volume adjustment
return super.dispatchKeyEvent(event);
} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
if (uniqueDown) {
hide();
}
return true;
}
show(sDefaultTimeout);
return super.dispatchKeyEvent(event);
}
private View.OnClickListener mPauseListener = new View.OnClickListener() {
public void onClick(View v) {
doPauseResume();
show(sDefaultTimeout);
}
};
private View.OnClickListener mFullscreenListener = new View.OnClickListener() {
public void onClick(View v) {
doToggleFullscreen();
show(sDefaultTimeout);
}
};
public void updatePausePlay() {
if (mRoot == null || mPauseButton == null || mPlayer == null) {
return;
}
if (mPlayer.isPlaying()) {
mPauseButton.setImageResource(R.drawable.ic_media_pause);
} else {
mPauseButton.setImageResource(R.drawable.ic_media_play);
}
}
public void updateFullScreen() {
if (mRoot == null || mFullscreenButton == null || mPlayer == null) {
return;
}
if (mPlayer.isFullScreen()) {
mFullscreenButton.setImageResource(R.drawable.ic_media_fullscreen_shrink);
}
else {
mFullscreenButton.setImageResource(R.drawable.ic_media_fullscreen_stretch);
}
}
private void doPauseResume() {
if (mPlayer == null) {
return;
}
if (mPlayer.isPlaying()) {
mPlayer.pause();
} else {
mPlayer.start();
}
updatePausePlay();
}
private void doToggleFullscreen() {
if (mPlayer == null) {
return;
}
mPlayer.toggleFullScreen();
}
// 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 (mPlayer == null) {
return;
}
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);
}
disableUnsupportedButtons();
super.setEnabled(enabled);
}
#Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(VideoControllerView.class.getName());
}
#Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(VideoControllerView.class.getName());
}
private View.OnClickListener mRewListener = new View.OnClickListener() {
public void onClick(View v) {
if (mPlayer == null) {
return;
}
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) {
if (mPlayer == null) {
return;
}
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();
boolean canPause();
boolean canSeekBackward();
boolean canSeekForward();
boolean isFullScreen();
void toggleFullScreen();
}
private static class MessageHandler extends Handler {
private final WeakReference<VideoControllerView> mView;
MessageHandler(VideoControllerView view) {
mView = new WeakReference<VideoControllerView>(view);
}
#Override
public void handleMessage(Message msg) {
VideoControllerView view = mView.get();
if (view == null || view.mPlayer == null) {
return;
}
int pos;
switch (msg.what) {
case FADE_OUT:
view.hide();
break;
case SHOW_PROGRESS:
pos = view.setProgress();
if (!view.mDragging && view.mShowing && view.mPlayer.isPlaying()) {
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, 1000 - (pos % 1000));
}
break;
}
}
}
}
Activity where i implemented the toggle screen logic for fullscreen:
#Override
public void toggleFullScreen() {
DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics);
android.widget.FrameLayout.LayoutParams params = (android.widget.FrameLayout.LayoutParams) videoSurface.getLayoutParams();
params.width = metrics.widthPixels;
params.height = metrics.heightPixels;
params.leftMargin = 0;
videoSurface.setLayoutParams(params);
}
for portrait mode the video is occupying the entire screen(video is stretched). how can i maintain the aspect ratio of the video with out stretching. by playing the video at center without loosing aspect ratio similar to youtube.
can any one please help me out in solving this issue. and let me know are there any open source projects for custom media controller with toggle screen options.
I am using pull to refresh in my application. Pull to refresh is working fine when the list size is crossing screen. But when the size is one or two there is a gap between the header and the listview saying tap to refresh.
Here is my code
public class PullToRefreshListView extends ListView implements OnScrollListener {
private static final int TAP_TO_REFRESH = 1;
private static final int PULL_TO_REFRESH = 2;
private static final int RELEASE_TO_REFRESH = 3;
private static final int REFRESHING = 4;
private static final String TAG = "PullToRefreshListView";
private OnRefreshListener mOnRefreshListener;
/**
* Listener that will receive notifications every time the list scrolls.
*/
private OnScrollListener mOnScrollListener;
private LayoutInflater mInflater;
private RelativeLayout mRefreshView;
private TextView mRefreshViewText;
private ImageView mRefreshViewImage;
private ProgressBar mRefreshViewProgress;
private TextView mRefreshViewLastUpdated;
private int mCurrentScrollState;
private int mRefreshState;
private RotateAnimation mFlipAnimation;
private RotateAnimation mReverseFlipAnimation;
private int mRefreshViewHeight;
private int mRefreshOriginalTopPadding;
private int mLastMotionY;
private boolean mBounceHack;
public PullToRefreshListView(Context context) {
super(context);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
// Load all of the animations we need in code rather than through XML
mFlipAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mFlipAnimation.setInterpolator(new LinearInterpolator());
mFlipAnimation.setDuration(250);
mFlipAnimation.setFillAfter(true);
mReverseFlipAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
mReverseFlipAnimation.setDuration(250);
mReverseFlipAnimation.setFillAfter(true);
mInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mRefreshView = (RelativeLayout) mInflater.inflate(
R.layout.pull_to_refresh_header, this, false);
mRefreshViewText = (TextView) mRefreshView
.findViewById(R.id.pull_to_refresh_text);
mRefreshViewImage = (ImageView) mRefreshView
.findViewById(R.id.pull_to_refresh_image);
mRefreshViewProgress = (ProgressBar) mRefreshView
.findViewById(R.id.pull_to_refresh_progress);
mRefreshViewLastUpdated = (TextView) mRefreshView
.findViewById(R.id.pull_to_refresh_updated_at);
mRefreshViewImage.setMinimumHeight(50);
mRefreshView.setOnClickListener(new OnClickRefreshListener());
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();
mRefreshState = TAP_TO_REFRESH;
addHeaderView(mRefreshView);
super.setOnScrollListener(this);
measureView(mRefreshView);
mRefreshViewHeight = mRefreshView.getMeasuredHeight();
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setSelection(1);
}
#Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
setSelection(1);
}
/**
* Set the listener that will receive notifications every time the list
* scrolls.
*
* #param l
* The scroll listener.
*/
#Override
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}
/**
* Register a callback to be invoked when this list should be refreshed.
*
* #param onRefreshListener
* The callback to run.
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
mOnRefreshListener = onRefreshListener;
}
/**
* Set a text to represent when the list was last updated.
*
* #param lastUpdated
* Last updated at.
*/
public void setLastUpdated(CharSequence lastUpdated) {
if (lastUpdated != null) {
mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
mRefreshViewLastUpdated.setText(lastUpdated);
} else {
mRefreshViewLastUpdated.setVisibility(View.GONE);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
final int y = (int) event.getY();
mBounceHack = false;
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (!isVerticalScrollBarEnabled()) {
setVerticalScrollBarEnabled(true);
}
if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
if ((mRefreshView.getBottom() >= mRefreshViewHeight || mRefreshView
.getTop() >= 0) && mRefreshState == RELEASE_TO_REFRESH) {
// Initiate the refresh
mRefreshState = REFRESHING;
prepareForRefresh();
onRefresh();
} else if (mRefreshView.getBottom() < mRefreshViewHeight
|| mRefreshView.getTop() <= 0) {
// Abort refresh and scroll down below the refresh view
resetHeader();
setSelection(1);
}
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}
private void applyHeaderPadding(MotionEvent ev) {
// getHistorySize has been available since API 1
int pointerCount = ev.getHistorySize();
for (int p = 0; p < pointerCount; p++) {
if (mRefreshState == RELEASE_TO_REFRESH) {
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
}
int historicalY = (int) ev.getHistoricalY(p);
// Calculate the padding to apply, we divide by 1.7 to
// simulate a more resistant effect during pull.
int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7);
mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
topPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
}
}
/**
* Sets the header padding back to original size.
*/
private void resetHeaderPadding() {
mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
/**
* Resets the header to the original state.
*/
private void resetHeader() {
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = TAP_TO_REFRESH;
resetHeaderPadding();
// Set refresh view text to the pull label
mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
// Replace refresh drawable with arrow drawable
mRefreshViewImage
.setImageResource(R.drawable.ic_pulltorefresh_arrow);
// Clear the full rotation animation
mRefreshViewImage.clearAnimation();
// Hide progress bar and arrow.
mRefreshViewImage.setVisibility(View.GONE);
mRefreshViewProgress.setVisibility(View.GONE);
}
}
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// When the refresh view is completely visible, change the text to say
// "Release to refresh..." and flip the arrow drawable.
if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& mRefreshState != REFRESHING) {
if (firstVisibleItem == 0) {
mRefreshViewImage.setVisibility(View.VISIBLE);
if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20 || mRefreshView
.getTop() >= 0) && mRefreshState != RELEASE_TO_REFRESH) {
mRefreshViewText
.setText(R.string.pull_to_refresh_release_label);
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mFlipAnimation);
mRefreshState = RELEASE_TO_REFRESH;
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) {
mRefreshViewText
.setText(R.string.pull_to_refresh_pull_label);
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
}
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE);
resetHeader();
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING
&& firstVisibleItem == 0 && mRefreshState != REFRESHING) {
setSelection(1);
mBounceHack = true;
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
setSelection(1);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
if (mCurrentScrollState == SCROLL_STATE_IDLE) {
mBounceHack = false;
}
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
public void prepareForRefresh() {
resetHeaderPadding();
mRefreshViewImage.setVisibility(View.GONE);
// We need this hack, otherwise it will keep the previous drawable.
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE);
// Set refresh view text to the refreshing label
mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
mRefreshState = REFRESHING;
}
public void onRefresh() {
Log.d(TAG, "onRefresh");
if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}
/**
* Resets the list to a normal state after a refresh.
*
* #param lastUpdated
* Last updated at.
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onRefreshComplete();
}
/**
* Resets the list to a normal state after a refresh.
*/
public void onRefreshComplete() {
Log.d(TAG, "onRefreshComplete");
resetHeader();
// If refresh view is visible when loading completes, scroll down to
// the next item.
if (mRefreshView.getBottom() > 0) {
invalidateViews();
setSelection(1);
}
}
/**
* Invoked when the refresh view is clicked on. This is mainly used when
* there's only a few items in the list and it's not possible to drag the
* list.
*/
private class OnClickRefreshListener implements OnClickListener {
#Override
public void onClick(View v) {
if (mRefreshState != REFRESHING) {
prepareForRefresh();
onRefresh();
}
}
}
/**
* Interface definition for a callback to be invoked when list should be
* refreshed.
*/
public interface OnRefreshListener {
/**
* Called when the list should be refreshed.
* <p>
* A call to {#link PullToRefreshListView #onRefreshComplete()} is
* expected to indicate that the refresh has completed.
*/
public void onRefresh();
}
}
Here is my xml code
<com.k2b.kluebook.pulltorefresh.PullToRefreshListView
android:id="#+id/list_pulltorefresh"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="#null"
android:dividerHeight="0dp" >
</com.k2b.kluebook.pulltorefresh.PullToRefreshListView>
Here is my class file code
listview.setOnRefreshListener(new OnRefreshListener() {
#Override
public void onRefresh() {
// Do work to refresh the list here.
}
});
How to get rid of the GAP and "Tap to Refresh".
Use this code instead
public class PullToRefreshListView extends ListView implements OnScrollListener {
// private static final int TAP_TO_REFRESH = 1;
private static final int PULL_TO_REFRESH = 2;
private static final int RELEASE_TO_REFRESH = 3;
protected static final int REFRESHING = 4;
protected static final String TAG = "PullToRefreshListView";
private OnRefreshListener mOnRefreshListener;
/**
* Listener that will receive notifications every time the list scrolls.
*/
private OnScrollListener mOnScrollListener;
protected LayoutInflater mInflater;
// header
private RelativeLayout mRefreshView;
private TextView mRefreshViewText;
private ImageView mRefreshViewImage;
private ProgressBar mRefreshViewProgress;
private TextView mRefreshViewLastUpdated;
protected int mCurrentScrollState;
protected int mRefreshState;
private RotateAnimation mFlipAnimation;
private RotateAnimation mReverseFlipAnimation;
private int mRefreshViewHeight;
private int mRefreshOriginalTopPadding;
private int mLastMotionY;
private boolean mBounceHack;
public PullToRefreshListView(Context context) {
super(context);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context);
}
protected void init(Context context) {
// Load all of the animations we need in code rather than through XML
mFlipAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mFlipAnimation.setInterpolator(new LinearInterpolator());
mFlipAnimation.setDuration(250);
mFlipAnimation.setFillAfter(true);
mReverseFlipAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
mReverseFlipAnimation.setDuration(250);
mReverseFlipAnimation.setFillAfter(true);
mInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// header
mRefreshView = (RelativeLayout) mInflater.inflate(
R.layout.pull_to_refresh_header, this, false);
mRefreshViewText = (TextView) mRefreshView
.findViewById(R.id.pull_to_refresh_text);
mRefreshViewImage = (ImageView) mRefreshView
.findViewById(R.id.pull_to_refresh_image);
mRefreshViewProgress = (ProgressBar) mRefreshView
.findViewById(R.id.pull_to_refresh_progress);
mRefreshViewLastUpdated = (TextView) mRefreshView
.findViewById(R.id.pull_to_refresh_updated_at);
mRefreshViewImage.setMinimumHeight(50);
mRefreshView.setOnClickListener(new OnClickRefreshListener());
mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();
mRefreshState = PULL_TO_REFRESH;
addHeaderView(mRefreshView);
super.setOnScrollListener(this);
measureView(mRefreshView);
mRefreshViewHeight = mRefreshView.getMeasuredHeight();
}
#Override
protected void onAttachedToWindow() {
//have to ask super to attach to window, otherwise it won't scroll in jelly bean.
super.onAttachedToWindow();
setSelection(1);
}
#Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
setSelection(1);
}
/**
* Set the listener that will receive notifications every time the list
* scrolls.
*
* #param l
* The scroll listener.
*/
#Override
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}
/**
* Register a callback to be invoked when this list should be refreshed.
*
* #param onRefreshListener
* The callback to run.
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
mOnRefreshListener = onRefreshListener;
}
/**
* Set a text to represent when the list was last updated.
*
* #param lastUpdated
* Last updated at.
*/
public void setLastUpdated(CharSequence lastUpdated) {
if (lastUpdated != null) {
mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
mRefreshViewLastUpdated.setText(lastUpdated);
} else {
mRefreshViewLastUpdated.setVisibility(View.GONE);
}
}
#SuppressLint("ClickableViewAccessibility")
#Override
public boolean onTouchEvent(MotionEvent event) {
final int y = (int) event.getY();
mBounceHack = false;
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (!isVerticalScrollBarEnabled()) {
setVerticalScrollBarEnabled(true);
}
if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
if ((mRefreshView.getBottom() >= mRefreshViewHeight || mRefreshView
.getTop() >= 0) && mRefreshState == RELEASE_TO_REFRESH) {
// Initiate the refresh
mRefreshState = REFRESHING;
prepareForRefresh();
onRefresh();
} else if (mRefreshView.getBottom() < mRefreshViewHeight
|| mRefreshView.getTop() <= 0) {
// Abort refresh and scroll down below the refresh view
resetHeader();
setSelection(1);
}
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}
private void applyHeaderPadding(MotionEvent ev) {
// getHistorySize has been available since API 1
int pointerCount = ev.getHistorySize();
for (int p = 0; p < pointerCount; p++) {
// if (mRefreshState == RELEASE_TO_REFRESH) {
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
}
int historicalY = (int) ev.getHistoricalY(p);
// Calculate the padding to apply, we divide by 1.7 to
// simulate a more resistant effect during pull.
int topPadding = (int) (((historicalY - mLastMotionY) - mRefreshViewHeight) / 1.7);
mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
topPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
// }
}
/**
* Sets the header padding back to original size.
*/
private void resetHeaderPadding() {
mLastMotionY = 0;
mRefreshView.setPadding(mRefreshView.getPaddingLeft(),
mRefreshOriginalTopPadding, mRefreshView.getPaddingRight(),
mRefreshView.getPaddingBottom());
}
/**
* Resets the header to the original state.
*/
private void resetHeader() {
// if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = PULL_TO_REFRESH;
resetHeaderPadding();
// Set refresh view text to the pull label
mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
// Replace refresh drawable with arrow drawable
mRefreshViewImage
.setImageResource(R.drawable.ic_pulltorefresh_arrow);
// Clear the full rotation animation
mRefreshViewImage.clearAnimation();
// Hide progress bar and arrow.
mRefreshViewImage.setVisibility(View.GONE);
mRefreshViewProgress.setVisibility(View.GONE);
// }
}
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// When the refresh view is completely visible, change the text to say
// "Release to refresh..." and flip the arrow drawable.
if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& mRefreshState != REFRESHING) {
if (firstVisibleItem == 0) {
mRefreshViewImage.setVisibility(View.VISIBLE);
if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20 || mRefreshView
.getTop() >= 0) && mRefreshState != RELEASE_TO_REFRESH) {
mRefreshViewText
.setText(R.string.pull_to_refresh_release_label);
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mFlipAnimation);
mRefreshState = RELEASE_TO_REFRESH;
} else if (mRefreshView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) {
mRefreshViewText
.setText(R.string.pull_to_refresh_pull_label);
// if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
// }
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE);
resetHeader();
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING
&& firstVisibleItem == 0 && mRefreshState != REFRESHING) {
setSelection(1);
mBounceHack = true;
} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {
setSelection(1);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
if (mCurrentScrollState == SCROLL_STATE_IDLE) {
mBounceHack = false;
}
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
public void prepareForRefresh() {
resetHeaderPadding();
mRefreshViewImage.setVisibility(View.GONE);
// We need this hack, otherwise it will keep the previous drawable.
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE);
// Set refresh view text to the refreshing label
mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
mRefreshState = REFRESHING;
}
public void onRefresh() {
Log.d(TAG, "onRefresh");
if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}
/**
* Resets the list to a normal state after a refresh.
*
* #param lastUpdated
* Last updated at.
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated);
onRefreshComplete();
}
/**
* Resets the list to a normal state after a refresh.
*/
public void onRefreshComplete() {
Log.d(TAG, "onRefreshComplete");
resetHeader();
// If refresh view is visible when loading completes, scroll down to
// the next item.
if (mRefreshView.getBottom() > 0) {
invalidateViews();
setSelection(1);
}
}
/**
* Invoked when the refresh view is clicked on. This is mainly used when
* there's only a few items in the list and it's not possible to drag the
* list.
*/
private class OnClickRefreshListener implements OnClickListener {
public void onClick(View v) {
if (mRefreshState != REFRESHING) {
prepareForRefresh();
onRefresh();
}
}
}
/**
* Interface definition for a callback to be invoked when list should be
* refreshed.
*/
public interface OnRefreshListener {
/**
* Called when the list should be refreshed.
* <p>
* A call to {#link PullToRefreshListView #onRefreshComplete()} is
* expected to indicate that the refresh has completed.
*/
public void onRefresh();
}
}
also set the visibility to gone in the pull_to_refresh_header.xml in your library layout if you have it (android:id="#+id/pull_to_refresh_text")
<TextView
android:id="#+id/pull_to_refresh_text"
android:text="#string/pull_to_refresh_pull_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold"
android:paddingTop="5dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:visibility="gone"
/>
enjoy!