I have developed a three-pane layout where up to two panes are visible at a time. Only in landscape mode multiple views are supported. Each pane contains arrows to change the visibility state so the panes distribution change.
When the application opens in portrait mode, the views transition works correctly. The same happens when the application is opened in landscape mode. The problem arises when device is rotated and the control should change the distribution of the panels. when device is rotated the state transition fails and panes disappear or are not in the position where they should be.
Due to the requirements of the application the activity host handle the configuration changes itself (android:configChanges="orientation|screensize") and I can not disable this behavior. When onConfigurationChanged method is invoked in the host activity the control is notified to execute the state transition. Depending on the current visibility state and the new orientation the control determines the new state and calls the method responsible for executing the transition (configureWidth method in the control's code). Below the code of the host activity and the control:
MainActivity.java
public class MainActivity extends Activity implements
CategoriesListFragment.OnCategoriesListSizeControlListener,
TasksListFragment.OnTasksListSizeControlListener,
TaskDetailFragment.OnTaskDetailSizeControlListener, OnStateChangeListener {
// Multipanel control that will contain the fragments associated with the
// category list, task list and task detail.
private ThreePaneLayout mMultiPaneControl;
// Fragments
private CategoriesListFragment mCategoriesListFragment;
private TasksListFragment mTasksListFragment;
private TaskDetailFragment mTaskDetailFragment;
private int mScreenOrientation;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the initial orientation of the device
mScreenOrientation = getResources().getConfiguration().orientation;
mMultiPaneControl = (ThreePaneLayout) findViewById(R.id.multiPaneControl);
mCategoriesListFragment = CategoriesListFragment.newInstance();
mTasksListFragment = TasksListFragment.newInstance();
mTaskDetailFragment = TaskDetailFragment.newInstance();
// Add the state change observers of the ThreePaneLayout control.
// The client fragments use the events of the OnstateChangeListener interface to
// update the arrows orientation that user can use to redimension the panels
mMultiPaneControl.addStateObserver(mCategoriesListFragment);
mMultiPaneControl.addStateObserver(mTasksListFragment);
mMultiPaneControl.addStateObserver(mTaskDetailFragment);
mMultiPaneControl.addStateObserver(this);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.categoriesList, mCategoriesListFragment, "tag");
transaction.add(R.id.tasksList, mTasksListFragment);
transaction.add(R.id.taskDetail, mTaskDetailFragment);
transaction.commit();
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mScreenOrientation = getResources().getConfiguration().orientation;
// When device orientationcchange notify the ThreePaneLyout control associated with the activity
// to update the distribution of its panels in order to improve usability
mMultiPaneControl.deviceOrientationHasChange();
}
#Override
public void onCategoriesListSizeControlSelected() {
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
VisibilityState visibilityState = mMultiPaneControl.getVisibityState();
if (visibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE ) {
mMultiPaneControl.setVisibilityState(VisibilityState.LEFT_VISIBLE);
} else if ( visibilityState == VisibilityState.LEFT_VISIBLE) {
mMultiPaneControl.setVisibilityState(VisibilityState.LEFT_AND_MIDDLE_VISIBLE);
}
} else { // Configuration.ORIENTATION_PORTRAIT
// The only possible state if this event is received while the device in
// portrait orientation is MIDDLE_VISIBLE
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_VISIBLE);
}
}
#Override
public void onTasksListSizeControlSelected(boolean leftControl) {
VisibilityState visibilityState = mMultiPaneControl.getVisibityState();
if (leftControl) {
if ( mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE ) {
if (visibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE ) {
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_VISIBLE);
} else if (visibilityState == VisibilityState.MIDDLE_VISIBLE ) {
mMultiPaneControl.setVisibilityState(VisibilityState.LEFT_AND_MIDDLE_VISIBLE);
} else if (visibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE ) {
mMultiPaneControl.setVisibilityState(VisibilityState.LEFT_AND_MIDDLE_VISIBLE);
}
} else { // Configuration.ORIENTATION_PORTRAIT
// The only possible state if this event is received while the device in
// portrait orientation is LEFT_VISIBLE
mMultiPaneControl.setVisibilityState(VisibilityState.LEFT_VISIBLE);
}
} else {
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
if (visibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_AND_RIGHT_VISIBLE);
} else if (visibilityState == VisibilityState.MIDDLE_VISIBLE) {
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_AND_RIGHT_VISIBLE);
} else if (visibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_VISIBLE);
}
} else { // Configuration.ORIENTATION_PORTRAIT
// The only possible state if this event is received while the device in
// portrait orientation is RIGHT_VISIBLE
mMultiPaneControl.setVisibilityState(VisibilityState.RIGHT_VISIBLE);
}
}
}
#Override
public void onDetailTaskSizeControlSelected() {
VisibilityState visibilityState = mMultiPaneControl.getVisibityState();
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
if ( visibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
mMultiPaneControl.setVisibilityState(VisibilityState.RIGHT_VISIBLE);
} else if ( visibilityState == VisibilityState.RIGHT_VISIBLE) {
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_AND_RIGHT_VISIBLE);
}
} else { // Configuration.ORIENTATION_PORTRAIT
// The only possible state if this event is received while the device in
// portrait orientation is MIDDLE_VISIBLE
mMultiPaneControl.setVisibilityState(VisibilityState.MIDDLE_VISIBLE);
}
}
#Override
public void onBeginTransitionState(VisibilityState oldState,
VisibilityState newState) {
}
#Override
public void onNewStateVisible(VisibilityState newState) { /**/ }
}
ThreePaneLayout.java
/**
* <p>Control to display up to three panels with a maximum of two simultaneously visible.</p>
*/
public class ThreePaneLayout extends LinearLayout {
/**
* Time control takes for the state change animation
*/
public static final int ANIMATION_DURATION = 300;
private boolean isScrollingViews;
/**
* Possible control visibility states. The states are exclusive, ie, if the control is
* in state {# link LEFT_AND_MIDDLE_VISIBLE} implies that the right pane is not visible.
*/
public enum VisibilityState {
LEFT_VISIBLE,
LEFT_AND_MIDDLE_VISIBLE,
MIDDLE_VISIBLE,
MIDDLE_AND_RIGHT_VISIBLE,
RIGHT_VISIBLE
}
/**
* Interface to be implemented by clients that require to be notified
* when the visibility state change.
*/
public interface OnStateChangeListener {
/**
* Method invoked by the control just prior to the transition state of visibility
*
* #param oldState Estado actual de visibilidad control
* #param newState Proximo estado de visibilidad del control
*/
void onBeginTransitionState(VisibilityState oldState, VisibilityState newState);
/**
* Method invoked by the control when its visibility status has been updated
*
* #param newState New visibility state of the control panels
*/
void onNewStateVisible(VisibilityState newState);
}
// Reference to the three panels of the control
private View mLeftView;
private View mMiddleView;
private View mRightView;
// Variables that store the minimum and maximum widths that can have different panels
private int mMinPaneWidth = -1;
private int mMaxPaneWidth;
private int mFullScreenWidth;
// Stores the current device orientation
private int mScreenOrientation;
// Reference to the current state of the panels
private VisibilityState mVisibilityState;
/**
* Lista de observadores de cambio de estado del control
*/
private List<OnStateChangeListener> mStateListeners;
private Handler mHandler = new Handler();
private Context mContext;
public ThreePaneLayout(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i("Log", "Llamado el constructor del control");
mContext = context;
mScreenOrientation = getResources().getConfiguration().orientation;
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
setOrientation(HORIZONTAL);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ThreePaneLayout);
String initialState = a.getString(R.styleable.ThreePaneLayout_initialState);
if ( initialState != null ) {
// The initial state has been defined in the XML
if (initialState.equals("left_visible")) {
mVisibilityState = VisibilityState.LEFT_VISIBLE;
} else if (initialState.equals("left_and_middle_visible")) {
mVisibilityState = VisibilityState.LEFT_AND_MIDDLE_VISIBLE;
}
} else {
// The initial state is not defined in the XML, set the default state
mVisibilityState = VisibilityState.LEFT_AND_MIDDLE_VISIBLE;
}
}
#Override
public void onFinishInflate() {
super.onFinishInflate();
if ( getChildCount() != 3 ) {
throw new IllegalStateException("ThreePaneLayout requires defining three daughters views in the XML");
}
// Get a reference to the views that make control
mLeftView = getChildAt(0);
mMiddleView = getChildAt(1);
mRightView = getChildAt(2);
configureWidth();
}
/**
* Set the weights of each of the views of the layout container.
*
* If the method takes no arguments recalculates the weights based on the current visibility
* state of the control. If this method receive a parameter is used as visibility state from
* which will be held on recalculation
*
* #param args
*/
private void configureWidth(VisibilityState ... args) {
LayoutParams leftPaneLayoutParams = (LayoutParams) mLeftView.getLayoutParams();
LayoutParams middlePaneLayoutParams = (LayoutParams) mMiddleView.getLayoutParams();
LayoutParams rightPaneLayoutParams = (LayoutParams) mRightView.getLayoutParams();
VisibilityState visibilityState = args.length == 0 ? mVisibilityState : args[0];
if (args.length > 0) {
leftPaneLayoutParams.width = 0;
middlePaneLayoutParams.width = 0;
rightPaneLayoutParams.width = 0;
}
if ( visibilityState == VisibilityState.LEFT_VISIBLE ) {
leftPaneLayoutParams.weight = 1.0f;
middlePaneLayoutParams.weight = 0.0f;
rightPaneLayoutParams.weight = 0.0f;
} else if ( visibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE ) {
leftPaneLayoutParams.weight = 0.35f;
middlePaneLayoutParams.weight = 0.65f;
rightPaneLayoutParams.weight = 0.0f;
} else if ( visibilityState == VisibilityState.MIDDLE_VISIBLE ) {
leftPaneLayoutParams.weight = 0.0f;
middlePaneLayoutParams.weight = 1.0f;
rightPaneLayoutParams.weight = 0.0f;
} else if ( visibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE ) {
leftPaneLayoutParams.weight = 0.0f;
middlePaneLayoutParams.weight = 0.35f;
rightPaneLayoutParams.weight = 0.65f;
} else if ( visibilityState == VisibilityState.RIGHT_VISIBLE ) {
leftPaneLayoutParams.weight = 0.0f;
middlePaneLayoutParams.weight = 0.0f;
rightPaneLayoutParams.weight = 1.0f;
}
// Refresh the view and compute the size of the view in the screen.
requestLayout();
}
/**
* Method that performs the visibility state transition control (redistribution of the panels)
*
* #param newVisibilityState New visibility state required.
* #param resetDimensions This parameter is optional and should be used only when you are performing a
* state transcion due to a change of device orientation so we can recalculated
* weights and widths of the panels.
*/
public void setVisibilityState(VisibilityState newVisibilityState, boolean ... resetDimensions) {
// Ignore the request if the control is being resized or if the requested state is equal to the current
if ( isScrollingViews || newVisibilityState == mVisibilityState ) {
return;
}
// If requested any state that contains more than one panel and device orientation is portrait,
// ignore the request (this control only supports multiple panels visible in landscape)
if (mScreenOrientation == Configuration.ORIENTATION_PORTRAIT) {
if ( newVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE ||
newVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE ) {
return;
}
}
if (resetDimensions.length > 0 && resetDimensions[0] == true) {
configureWidth(newVisibilityState);
mMinPaneWidth = -1;
}
// Calculate the maximum and minimum widths of the control panel if the have not been defined
if (mMinPaneWidth == -1) {
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(displayMetrics);
int screenWidth = displayMetrics.widthPixels;
if ( mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE ) {
mMinPaneWidth = (int) (screenWidth * 0.35);
mMaxPaneWidth = screenWidth - mMinPaneWidth;
mFullScreenWidth = screenWidth;
} else { // Configuration.ORIENTATION_PORTRAIT
mMinPaneWidth = mMaxPaneWidth = mFullScreenWidth = screenWidth;
}
resetWidget(mLeftView, mMinPaneWidth);
resetWidget(mMiddleView, mMaxPaneWidth);
resetWidget(mRightView, mMaxPaneWidth);
requestLayout();
}
isScrollingViews = true;
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
isScrollingViews = false;
}
}, ANIMATION_DURATION + 100);
VisibilityState currentVisibilityState = mVisibilityState;
// Notify control observers will produce a state transition
if ( mStateListeners != null ) {
for ( OnStateChangeListener observer : mStateListeners ) {
observer.onBeginTransitionState(currentVisibilityState, newVisibilityState);
}
}
if (resetDimensions.length == 0) {
// Perform the movement of the panels to match the new state required
animateVisibilityStateTransition(currentVisibilityState, newVisibilityState);
}
// Update the reference to the current state
mVisibilityState = newVisibilityState;
// Notify the new control state to the control's observers
if ( mStateListeners != null ) {
for ( OnStateChangeListener observer : mStateListeners ) {
observer.onNewStateVisible(mVisibilityState);
}
}
}
/**
* Moves on the x axis and resize the panels to suit new visibility state required.
*
* #param currentVisibilityState Current state control visibility
* #param requiredVisibilityState New visibility state required
*/
private void animateVisibilityStateTransition(VisibilityState currentVisibilityState,
VisibilityState requiredVisibilityState) {
switch (requiredVisibilityState) {
case LEFT_VISIBLE:
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
if (currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
ObjectAnimator.ofInt(this, "leftWidth", mMinPaneWidth, mFullScreenWidth)
.setDuration(ANIMATION_DURATION).start();
}
} else { // Configuration.ORIENTATION_PORTRAIT
if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
translateView(mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
} else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
translateView(2 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
}
}
break;
case LEFT_AND_MIDDLE_VISIBLE:
// Este estado solo es posible en orientacion panoramica
if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
translateView(mMinPaneWidth, mLeftView, mMiddleView, mRightView);
ObjectAnimator.ofInt(this, "middleWidth", mFullScreenWidth, mMaxPaneWidth)
.setDuration(ANIMATION_DURATION).start();
} else if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
translateView(mMinPaneWidth, mLeftView, mMiddleView, mRightView);
ObjectAnimator.ofInt(this, "middleWidth", mMinPaneWidth, mMaxPaneWidth)
.setDuration(ANIMATION_DURATION).start();
} else if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
ObjectAnimator.ofInt(this, "leftWidth", mFullScreenWidth, mMinPaneWidth)
.setDuration(ANIMATION_DURATION).start();
}
break;
case MIDDLE_VISIBLE:
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
if (currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
translateView(-1 * mMinPaneWidth, mLeftView, mMiddleView, mRightView);
ObjectAnimator.ofInt(this, "middleWidth", mMaxPaneWidth, mFullScreenWidth)
.setDuration(ANIMATION_DURATION).start();
} else if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
ObjectAnimator.ofInt(this, "middleWidth", mMinPaneWidth, mFullScreenWidth)
.setDuration(ANIMATION_DURATION).start();
}
} else { // Configuration.ORIENTATION_PORTRAIT
if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
translateView(-1 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
} else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
translateView(mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
}
}
break;
case MIDDLE_AND_RIGHT_VISIBLE:
// Este estado solo es posible en orientacion panoramica
if (currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
translateView(-1 * mMinPaneWidth, mLeftView, mMiddleView, mRightView);
ObjectAnimator.ofInt(this, "middleWidth", mMaxPaneWidth, mMinPaneWidth)
.setDuration(ANIMATION_DURATION).start();
} else if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
ObjectAnimator.ofInt(this, "middleWidth", mFullScreenWidth, mMinPaneWidth)
.setDuration(ANIMATION_DURATION).start();
} else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
translateView(mMinPaneWidth, mLeftView, mMiddleView, mRightView);
ObjectAnimator.ofInt(this, "rightWidth", mFullScreenWidth, mMaxPaneWidth)
.setDuration(ANIMATION_DURATION).start();
}
break;
case RIGHT_VISIBLE:
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
translateView(-1 * mMinPaneWidth, mLeftView, mMiddleView, mRightView);
ObjectAnimator.ofInt(this, "rightWidth", mMaxPaneWidth, mFullScreenWidth)
.setDuration(ANIMATION_DURATION).start();
}
} else { // Configuration.ORIENTATION_PORTRAIT
if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
translateView(-2 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
} else if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
translateView(-1 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
}
}
break;
}
}
public VisibilityState getVisibityState() {
return mVisibilityState;
}
#SuppressWarnings("unused")
private void setLeftWidth(int value) {
mLeftView.getLayoutParams().width = value;
requestLayout();
}
#SuppressWarnings("unused")
private void setMiddleWidth(int value) {
mMiddleView.getLayoutParams().width = value;
requestLayout();
}
#SuppressWarnings("unused")
private void setRightWidth(int value) {
mRightView.getLayoutParams().width = value;
requestLayout();
}
/**
* Moves in the X axis the views received as parameter.
*
* #param deltaX Number of pixels that are shifted in the x-axis views
* #param views Views on which it will move
*/
private void translateView(int deltaX, View... views) {
for (final View view : views) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
view.animate().translationXBy(deltaX).setDuration(ANIMATION_DURATION)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
}
}
/**
* Updates the properties of length and weight on the layout of the view passed as parameter
*
* #param view Vista on which perform the update of the properties
* #param width New width
*/
private void resetWidget(View view, int width) {
LinearLayout.LayoutParams p = (LinearLayout.LayoutParams) view.getLayoutParams();
p.width = width;
p.weight = 0;
}
/**
* Adds the component passed as a parameter to the list of control observers. The observers are notified
* every time the control change the panels distribution. Observers also receive a notification just before
* starting the transition state by the control.
*
* #param observer component that implements the {# link OnStateChangeListener} interface
* to recieve notifications when the visibility control state change
*/
public void addStateObserver(OnStateChangeListener observer) {
if ( mStateListeners == null ) {
mStateListeners = new ArrayList<OnStateChangeListener>();
}
mStateListeners.add(observer);
}
/**
* Removes the component passed as parameter from the list of observers of
* state change. Observers are added to the list through the method {# link # addStateObserver}
*
* #param observer component that implements the {# link OnStateChangeListener} interface
* to recieve notifications when the visibility control state change
*/
public void deleteStatetObserver(OnStateChangeListener observer) {
if ( mStateListeners == null ) return;
mStateListeners.remove(observer);
}
/**
* Method invoked by the host activity when the orientation of the device has changed. Based on the
* new orientation the control redistributes panels to match the new orientation and improve usability
*/
public void deviceOrientationHasChange()
{
int newScreenOrientation = getResources().getConfiguration().orientation;
VisibilityState currentVisibilityState = getVisibityState();
VisibilityState newVisibilityState = null;
if (newScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
// When orientation chamge to landscape the only possible states in the previous orientation
// (portrait) can only be those where not coexist multiple views (eg LEFT_VISIBLE,
// MIDDLE_VISIBLE, RIGHT_VISIBLE)
if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
newVisibilityState = VisibilityState.LEFT_AND_MIDDLE_VISIBLE;
} else if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
newVisibilityState = VisibilityState.MIDDLE_VISIBLE;
} else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
newVisibilityState = VisibilityState.RIGHT_VISIBLE;
}
} else { // Configuration.ORIENTATION_PORTRAIT
if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
newVisibilityState = VisibilityState.LEFT_VISIBLE;
} else if ( currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE ||
currentVisibilityState == VisibilityState.MIDDLE_VISIBLE ) {
newVisibilityState = VisibilityState.MIDDLE_VISIBLE;
} else if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
newVisibilityState = VisibilityState.RIGHT_VISIBLE;
} else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
newVisibilityState = VisibilityState.RIGHT_VISIBLE;
}
}
mScreenOrientation = newScreenOrientation;
mMinPaneWidth = -1;
configureWidth(newVisibilityState);
mVisibilityState = newVisibilityState;
}
public View getLeftView() {
return mLeftView;
}
public View getMiddleView() {
return mMiddleView;
}
public View getRightView() {
return mRightView;
}
}
For example when the device is in landscape and the visibility state is MIDDLE AND RIGHT (see Figure 1) when you rotate the device to portrait the state change fails (see Figure 2).
Figure 1
Figure 2
For a more detailed analysis of the application, I posted on github a sample application that uses the control and is available for download at the following link: https://github.com/dfpalomar/ThreePaneLayout
I tried to force the redraw of the views using the methods requestLayout, invalidate and forceLayout from controls's configureWidth method but without the desired results.
Any suggestions on how I could solve the problem is welcome :D
There is no automatic functionality to keep session when changing screen rotation, you need to do this manually yourself since the activity is being reloaded. You can read the details here:
http://developer.android.com/guide/topics/manifest/activity-element.html#config
There are also many similar questions here on StackOverflow, so please also try searching for this to get a suitable answer.
Related
I'm using custom SnapHelper which extends LinearSnapHelper to automatically scroll in recycler view.Code can be found below. The scrolling however is way too fast and i want to add a slide or fade animation. Please guideLink to scrolling effect in which animation needs to be added
Auto scroll code(Fragment1.kt)-
private fun autoScrollRecyclerView() {
val snapHelper = GravitySnapHelper(Gravity.START)
snapHelper.attachToRecyclerView(binding.rcvSuccessMetric)
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
if (layoutManager.findLastCompletelyVisibleItemPosition() < (adapter.itemCount) - 1)
layoutManager.smoothScrollToPosition(
binding.rcvSuccessMetric,
RecyclerView.State(),
layoutManager.findLastCompletelyVisibleItemPosition() + 2
)
else
layoutManager.smoothScrollToPosition(
binding.rcvSuccessMetric,
RecyclerView.State(),
0
)
}
}, 0, 6000)
}
GravitySnapHelper.java (Custom class)-
public class GravitySnapHelper extends LinearSnapHelper {
public static final int FLING_DISTANCE_DISABLE = -1;
public static final float FLING_SIZE_FRACTION_DISABLE = -1f;
private int gravity;
private boolean isRtl;
private boolean snapLastItem;
private int nextSnapPosition;
private boolean isScrolling = false;
private boolean snapToPadding = false;
private float scrollMsPerInch = 100f;
private int maxFlingDistance = FLING_DISTANCE_DISABLE;
private float maxFlingSizeFraction = FLING_SIZE_FRACTION_DISABLE;
private OrientationHelper verticalHelper;
private OrientationHelper horizontalHelper;
private GravitySnapHelper.SnapListener listener;
private RecyclerView recyclerView;
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
GravitySnapHelper.this.onScrollStateChanged(newState);
}
};
public GravitySnapHelper(int gravity) {
this(gravity, false, null);
}
public GravitySnapHelper(int gravity, #NonNull SnapListener snapListener) {
this(gravity, false, snapListener);
}
public GravitySnapHelper(int gravity, boolean enableSnapLastItem) {
this(gravity, enableSnapLastItem, null);
}
public GravitySnapHelper(int gravity, boolean enableSnapLastItem,
#Nullable SnapListener snapListener) {
if (gravity != Gravity.START
&& gravity != Gravity.END
&& gravity != Gravity.BOTTOM
&& gravity != Gravity.TOP
&& gravity != Gravity.CENTER) {
throw new IllegalArgumentException("Invalid gravity value. Use START " +
"| END | BOTTOM | TOP | CENTER constants");
}
this.snapLastItem = enableSnapLastItem;
this.gravity = gravity;
this.listener = snapListener;
}
#Override
public void attachToRecyclerView(#Nullable RecyclerView recyclerView)
throws IllegalStateException {
if (this.recyclerView != null) {
this.recyclerView.removeOnScrollListener(scrollListener);
}
if (recyclerView != null) {
recyclerView.setOnFlingListener(null);
if (gravity == Gravity.START || gravity == Gravity.END) {
isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
== ViewCompat.LAYOUT_DIRECTION_RTL;
}
recyclerView.addOnScrollListener(scrollListener);
this.recyclerView = recyclerView;
} else {
this.recyclerView = null;
}
super.attachToRecyclerView(recyclerView);
}
#Override
#Nullable
public View findSnapView(#NonNull RecyclerView.LayoutManager lm) {
return findSnapView(lm, true);
}
#Nullable
public View findSnapView(#NonNull RecyclerView.LayoutManager lm, boolean checkEdgeOfList) {
View snapView = null;
switch (gravity) {
case Gravity.START:
snapView = findView(lm, getHorizontalHelper(lm), Gravity.START, checkEdgeOfList);
break;
case Gravity.END:
snapView = findView(lm, getHorizontalHelper(lm), Gravity.END, checkEdgeOfList);
break;
case Gravity.TOP:
snapView = findView(lm, getVerticalHelper(lm), Gravity.START, checkEdgeOfList);
break;
case Gravity.BOTTOM:
snapView = findView(lm, getVerticalHelper(lm), Gravity.END, checkEdgeOfList);
break;
case Gravity.CENTER:
if (lm.canScrollHorizontally()) {
snapView = findView(lm, getHorizontalHelper(lm), Gravity.CENTER,
checkEdgeOfList);
} else {
snapView = findView(lm, getVerticalHelper(lm), Gravity.CENTER,
checkEdgeOfList);
}
break;
}
if (snapView != null) {
nextSnapPosition = recyclerView.getChildAdapterPosition(snapView);
} else {
nextSnapPosition = RecyclerView.NO_POSITION;
}
return snapView;
}
#Override
#NonNull
public int[] calculateDistanceToFinalSnap(#NonNull RecyclerView.LayoutManager layoutManager,
#NonNull View targetView) {
if (gravity == Gravity.CENTER) {
//noinspection ConstantConditions
return super.calculateDistanceToFinalSnap(layoutManager, targetView);
}
int[] out = new int[2];
if (!(layoutManager instanceof LinearLayoutManager)) {
return out;
}
LinearLayoutManager lm = (LinearLayoutManager) layoutManager;
if (lm.canScrollHorizontally()) {
if ((isRtl && gravity == Gravity.END) || (!isRtl && gravity == Gravity.START)) {
out[0] = getDistanceToStart(targetView, getHorizontalHelper(lm));
} else {
out[0] = getDistanceToEnd(targetView, getHorizontalHelper(lm));
}
} else if (lm.canScrollVertically()) {
if (gravity == Gravity.TOP) {
out[1] = getDistanceToStart(targetView, getVerticalHelper(lm));
} else {
out[1] = getDistanceToEnd(targetView, getVerticalHelper(lm));
}
}
return out;
}
#Override
#NonNull
public int[] calculateScrollDistance(int velocityX, int velocityY) {
if (recyclerView == null
|| (verticalHelper == null && horizontalHelper == null)
|| (maxFlingDistance == FLING_DISTANCE_DISABLE
&& maxFlingSizeFraction == FLING_SIZE_FRACTION_DISABLE)) {
return super.calculateScrollDistance(velocityX, velocityY);
}
final int[] out = new int[2];
Scroller scroller = new Scroller(recyclerView.getContext(),
new DecelerateInterpolator());
int maxDistance = getFlingDistance();
scroller.fling(0, 0, velocityX, velocityY,
-maxDistance, maxDistance,
-maxDistance, maxDistance);
out[0] = scroller.getFinalX();
out[1] = scroller.getFinalY();
return out;
}
#Nullable
#Override
public RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)
|| recyclerView == null) {
return null;
}
return new LinearSmoothScroller(recyclerView.getContext()) {
#Override
protected void onTargetFound(View targetView,
RecyclerView.State state,
RecyclerView.SmoothScroller.Action action) {
if (recyclerView == null || recyclerView.getLayoutManager() == null) {
// The associated RecyclerView has been removed so there is no action to take.
return;
}
int[] snapDistances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(),
targetView);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return scrollMsPerInch / displayMetrics.densityDpi;
}
};
}
/**
* Sets a {#link SnapListener} to listen for snap events
*
* #param listener a {#link SnapListener} that'll receive snap events or null to clear it
*/
public void setSnapListener(#Nullable SnapListener listener) {
this.listener = listener;
}
/**
* Changes the gravity of this {#link GravitySnapHelper}
* and dispatches a smooth scroll for the new snap position.
*
* #param newGravity one of the following: {#link Gravity#START}, {#link Gravity#TOP},
* {#link Gravity#END}, {#link Gravity#BOTTOM}, {#link Gravity#CENTER}
* #param smooth true if we should smooth scroll to new edge, false otherwise
*/
public void setGravity(int newGravity, Boolean smooth) {
if (this.gravity != newGravity) {
this.gravity = newGravity;
updateSnap(smooth, false);
}
}
/**
* Updates the current view to be snapped
*
* #param smooth true if we should smooth scroll, false otherwise
* #param checkEdgeOfList true if we should check if we're at an edge of the list
* and snap according to {#link GravitySnapHelper#getSnapLastItem()},
* or false to force snapping to the nearest view
*/
public void updateSnap(Boolean smooth, Boolean checkEdgeOfList) {
if (recyclerView == null || recyclerView.getLayoutManager() == null) {
return;
}
final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
View snapView = findSnapView(lm, checkEdgeOfList);
if (snapView != null) {
int[] out = calculateDistanceToFinalSnap(lm, snapView);
if (smooth) {
recyclerView.smoothScrollBy(out[0], out[1]);
} else {
recyclerView.scrollBy(out[0], out[1]);
}
}
}
/**
* This method will only work if there's a ViewHolder for the given position.
*
* #return true if scroll was successful, false otherwise
*/
public boolean scrollToPosition(int position) {
if (position == RecyclerView.NO_POSITION) {
return false;
}
return scrollTo(position, false);
}
/**
* Unlike {#link GravitySnapHelper#scrollToPosition(int)},
* this method will generally always find a snap view if the position is valid.
* <p>
* The smooth scroller from {#link GravitySnapHelper#createScroller(RecyclerView.LayoutManager)}
* will be used, and so will {#link GravitySnapHelper#scrollMsPerInch} for the scroll velocity
*
* #return true if scroll was successful, false otherwise
*/
public boolean smoothScrollToPosition(int position) {
if (position == RecyclerView.NO_POSITION) {
return false;
}
return scrollTo(position, true);
}
/**
* Get the current gravity being applied
*
* #return one of the following: {#link Gravity#START}, {#link Gravity#TOP}, {#link Gravity#END},
* {#link Gravity#BOTTOM}, {#link Gravity#CENTER}
*/
public int getGravity() {
return this.gravity;
}
/**
* Changes the gravity of this {#link GravitySnapHelper}
* and dispatches a smooth scroll for the new snap position.
*
* #param newGravity one of the following: {#link Gravity#START}, {#link Gravity#TOP},
* {#link Gravity#END}, {#link Gravity#BOTTOM}, {#link Gravity#CENTER}
*/
public void setGravity(int newGravity) {
setGravity(newGravity, true);
}
/**
* #return true if this SnapHelper should snap to the last item
*/
public boolean getSnapLastItem() {
return snapLastItem;
}
/**
* Enable snapping of the last item that's snappable.
* The default value is false, because you can't see the last item completely
* if this is enabled.
*
* #param snap true if you want to enable snapping of the last snappable item
*/
public void setSnapLastItem(boolean snap) {
snapLastItem = snap;
}
/**
* #return last distance set through {#link GravitySnapHelper#setMaxFlingDistance(int)}
* or {#link GravitySnapHelper#FLING_DISTANCE_DISABLE} if we're not limiting the fling distance
*/
public int getMaxFlingDistance() {
return maxFlingDistance;
}
/**
* Changes the max fling distance in absolute values.
*
* #param distance max fling distance in pixels
* or {#link GravitySnapHelper#FLING_DISTANCE_DISABLE}
* to disable fling limits
*/
public void setMaxFlingDistance(#Px int distance) {
maxFlingDistance = distance;
maxFlingSizeFraction = FLING_SIZE_FRACTION_DISABLE;
}
/**
* #return last distance set through {#link GravitySnapHelper#setMaxFlingSizeFraction(float)}
* or {#link GravitySnapHelper#FLING_SIZE_FRACTION_DISABLE}
* if we're not limiting the fling distance
*/
public float getMaxFlingSizeFraction() {
return maxFlingSizeFraction;
}
/**
* Changes the max fling distance depending on the available size of the RecyclerView.
* <p>
* Example: if you pass 0.5f and the RecyclerView measures 600dp,
* the max fling distance will be 300dp.
*
* #param fraction size fraction to be used for the max fling distance
* or {#link GravitySnapHelper#FLING_SIZE_FRACTION_DISABLE}
* to disable fling limits
*/
public void setMaxFlingSizeFraction(float fraction) {
maxFlingDistance = FLING_DISTANCE_DISABLE;
maxFlingSizeFraction = fraction;
}
/**
* #return last scroll speed set through {#link GravitySnapHelper#setScrollMsPerInch(float)}
* or 100f
*/
public float getScrollMsPerInch() {
return scrollMsPerInch;
}
/**
* Sets the scroll duration in ms per inch.
* <p>
* Default value is 100.0f
* <p>
* This value will be used in
* {#link GravitySnapHelper#createScroller(RecyclerView.LayoutManager)}
*
* #param ms scroll duration in ms per inch
*/
public void setScrollMsPerInch(float ms) {
scrollMsPerInch = ms;
}
/**
* #return true if this SnapHelper should snap to the padding. Defaults to false.
*/
public boolean getSnapToPadding() {
return snapToPadding;
}
/**
* If true, GravitySnapHelper will snap to the gravity edge
* plus any amount of padding that was set in the RecyclerView.
* <p>
* The default value is false.
*
* #param snapToPadding true if you want to snap to the padding
*/
public void setSnapToPadding(boolean snapToPadding) {
this.snapToPadding = snapToPadding;
}
/**
* #return the position of the current view that's snapped
* or {#link RecyclerView#NO_POSITION} in case there's none.
*/
public int getCurrentSnappedPosition() {
if (recyclerView != null && recyclerView.getLayoutManager() != null) {
View snappedView = findSnapView(recyclerView.getLayoutManager());
if (snappedView != null) {
return recyclerView.getChildAdapterPosition(snappedView);
}
}
return RecyclerView.NO_POSITION;
}
private int getFlingDistance() {
if (maxFlingSizeFraction != FLING_SIZE_FRACTION_DISABLE) {
if (verticalHelper != null) {
return (int) (recyclerView.getHeight() * maxFlingSizeFraction);
} else if (horizontalHelper != null) {
return (int) (recyclerView.getWidth() * maxFlingSizeFraction);
} else {
return Integer.MAX_VALUE;
}
} else if (maxFlingDistance != FLING_DISTANCE_DISABLE) {
return maxFlingDistance;
} else {
return Integer.MAX_VALUE;
}
}
/**
* #return true if the scroll will snap to a view, false otherwise
*/
private boolean scrollTo(int position, boolean smooth) {
if (recyclerView.getLayoutManager() != null) {
if (smooth) {
RecyclerView.SmoothScroller smoothScroller
= createScroller(recyclerView.getLayoutManager());
if (smoothScroller != null) {
smoothScroller.setTargetPosition(position);
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
return true;
}
} else {
RecyclerView.ViewHolder viewHolder
= recyclerView.findViewHolderForAdapterPosition(position);
if (viewHolder != null) {
int[] distances = calculateDistanceToFinalSnap(recyclerView.getLayoutManager(),
viewHolder.itemView);
recyclerView.scrollBy(distances[0], distances[1]);
return true;
}
}
}
return false;
}
private int getDistanceToStart(View targetView, #NonNull OrientationHelper helper) {
int distance;
// If we don't care about padding, just snap to the start of the view
if (!snapToPadding) {
int childStart = helper.getDecoratedStart(targetView);
if (childStart >= helper.getStartAfterPadding() / 2) {
distance = childStart - helper.getStartAfterPadding();
} else {
distance = childStart;
}
} else {
distance = helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();
}
return distance;
}
private int getDistanceToEnd(View targetView, #NonNull OrientationHelper helper) {
int distance;
if (!snapToPadding) {
int childEnd = helper.getDecoratedEnd(targetView);
if (childEnd >= helper.getEnd() - (helper.getEnd() - helper.getEndAfterPadding()) / 2) {
distance = helper.getDecoratedEnd(targetView) - helper.getEnd();
} else {
distance = childEnd - helper.getEndAfterPadding();
}
} else {
distance = helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding();
}
return distance;
}
/**
* Returns the first view that we should snap to.
*
* #param layoutManager the RecyclerView's LayoutManager
* #param helper orientation helper to calculate view sizes
* #param gravity gravity to find the closest view
* #return the first view in the LayoutManager to snap to, or null if we shouldn't snap to any
*/
#Nullable
private View findView(#NonNull RecyclerView.LayoutManager layoutManager,
#NonNull OrientationHelper helper,
int gravity,
boolean checkEdgeOfList) {
if (layoutManager.getChildCount() == 0 || !(layoutManager instanceof LinearLayoutManager)) {
return null;
}
final LinearLayoutManager lm = (LinearLayoutManager) layoutManager;
// If we're at an edge of the list, we shouldn't snap
// to avoid having the last item not completely visible.
if (checkEdgeOfList && (isAtEdgeOfList(lm) && !snapLastItem)) {
return null;
}
View edgeView = null;
int distanceToTarget = Integer.MAX_VALUE;
final int center;
if (layoutManager.getClipToPadding()) {
center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
center = helper.getEnd() / 2;
}
final boolean snapToStart = (gravity == Gravity.START && !isRtl)
|| (gravity == Gravity.END && isRtl);
final boolean snapToEnd = (gravity == Gravity.START && isRtl)
|| (gravity == Gravity.END && !isRtl);
for (int i = 0; i < lm.getChildCount(); i++) {
View currentView = lm.getChildAt(i);
int currentViewDistance;
if (snapToStart) {
if (!snapToPadding) {
currentViewDistance = Math.abs(helper.getDecoratedStart(currentView));
} else {
currentViewDistance = Math.abs(helper.getStartAfterPadding()
- helper.getDecoratedStart(currentView));
}
} else if (snapToEnd) {
if (!snapToPadding) {
currentViewDistance = Math.abs(helper.getDecoratedEnd(currentView)
- helper.getEnd());
} else {
currentViewDistance = Math.abs(helper.getEndAfterPadding()
- helper.getDecoratedEnd(currentView));
}
} else {
currentViewDistance = Math.abs(helper.getDecoratedStart(currentView)
+ (helper.getDecoratedMeasurement(currentView) / 2) - center);
}
if (currentViewDistance < distanceToTarget) {
distanceToTarget = currentViewDistance;
edgeView = currentView;
}
}
return edgeView;
}
private boolean isAtEdgeOfList(LinearLayoutManager lm) {
if ((!lm.getReverseLayout() && gravity == Gravity.START)
|| (lm.getReverseLayout() && gravity == Gravity.END)
|| (!lm.getReverseLayout() && gravity == Gravity.TOP)
|| (lm.getReverseLayout() && gravity == Gravity.BOTTOM)) {
return lm.findLastCompletelyVisibleItemPosition() == lm.getItemCount() - 1;
} else if (gravity == Gravity.CENTER) {
return lm.findFirstCompletelyVisibleItemPosition() == 0
|| lm.findLastCompletelyVisibleItemPosition() == lm.getItemCount() - 1;
} else {
return lm.findFirstCompletelyVisibleItemPosition() == 0;
}
}
/**
* Dispatches a {#link SnapListener#onSnap(int)} event if the snapped position
* is different than {#link RecyclerView#NO_POSITION}.
* <p>
* When {#link GravitySnapHelper#findSnapView(RecyclerView.LayoutManager)} returns null,
* {#link GravitySnapHelper#dispatchSnapChangeWhenPositionIsUnknown()} is called
*
* #param newState the new RecyclerView scroll state
*/
private void onScrollStateChanged(int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE && listener != null) {
if (isScrolling) {
if (nextSnapPosition != RecyclerView.NO_POSITION) {
listener.onSnap(nextSnapPosition);
} else {
dispatchSnapChangeWhenPositionIsUnknown();
}
}
}
isScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
}
/**
* Calls {#link GravitySnapHelper#findSnapView(RecyclerView.LayoutManager, boolean)}
* without the check for the edge of the list.
* <p>
* This makes sure that a position is reported in {#link SnapListener#onSnap(int)}
*/
private void dispatchSnapChangeWhenPositionIsUnknown() {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager == null) {
return;
}
View snapView = findSnapView(layoutManager, false);
if (snapView == null) {
return;
}
int snapPosition = recyclerView.getChildAdapterPosition(snapView);
if (snapPosition != RecyclerView.NO_POSITION) {
listener.onSnap(snapPosition);
}
}
private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
if (verticalHelper == null || verticalHelper.getLayoutManager() != layoutManager) {
verticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
}
return verticalHelper;
}
private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
if (horizontalHelper == null || horizontalHelper.getLayoutManager() != layoutManager) {
horizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
}
return horizontalHelper;
}
/**
* A listener that's called when the {#link RecyclerView} used by {#link GravitySnapHelper}
* changes its scroll state to {#link RecyclerView#SCROLL_STATE_IDLE}
* and there's a valid snap position.
*/
public interface SnapListener {
/**
* #param position last position snapped to
*/
void onSnap(int position);
}
}
How do I detect landscape mode while maintaining my layouts in portrait mode?
I have an activity which maintains the portrait mode irrespective of actual mobile orientation. I can do this by adding android:screenOrientation="portrait" to my activity configuration in manifest.
However, I want to detect screenOrientation while maintaining the activity in portrait.
I have tried with following code:
#Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
recordConfigChanges(newConfig); // Performs some variable changes or similar
}
// Maintain activity in portrait mode itself
Configuration customConfig = newConfig;
customConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
super.onConfigurationChanged(customConfig);
}
The above code does not maintain portrait, it rotates the UI to landscape mode.
Re-iterating the original question, how do I detect landscape mode while maintaining my layouts in portrait mode?
You can use CustomOrientationEventListener without affect actual Orientation change
in your activity
...
private CustomOrientationEventListener customOrientationEventListener;
...
customOrientationEventListener = new CustomOrientationEventListener(mContext) {
#Override
public void onSimpleOrientationChanged(int orientation) {
}
};
customOrientationEventListener.enable();
standalone class CustomOrientationEventListener
public abstract class CustomOrientationEventListener extends OrientationEventListener {
private String TAG="CustomOrientation";
private static final int CONFIGURATION_ORIENTATION_UNDEFINED = Configuration.ORIENTATION_UNDEFINED;
private volatile int defaultScreenOrientation = CONFIGURATION_ORIENTATION_UNDEFINED;
private int prevOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
private Context ctx;
private final int ROTATION_O = 1;
private final int ROTATION_90 = 2;
private final int ROTATION_180 = 3;
private final int ROTATION_270 = 4;
private int rotation = 0;
private ReentrantLock lock = new ReentrantLock(true);
public CustomOrientationEventListener(Context context) {
super(context);
ctx = context;
}
public CustomOrientationEventListener(Context context, int rate) {
super(context, rate);
ctx = context;
}
#Override
public void onOrientationChanged(final int orientation) {
//return if auto rotate disabled. should rotate if auto rotate enabled only
if (android.provider.Settings.System.getInt(ctx.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 0) // 0 = Auto Rotate Disabled
return;
int currentOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
if (orientation >= 340 || orientation < 20 && rotation != ROTATION_O) {
currentOrientation = Surface.ROTATION_0;
rotation = ROTATION_O;
} else if (orientation >= 70 && orientation < 110 && rotation != ROTATION_90) {
currentOrientation = Surface.ROTATION_90;
rotation = ROTATION_90;
} else if (orientation >= 160 && orientation < 200 && rotation != ROTATION_180) {
currentOrientation = Surface.ROTATION_180;
rotation = ROTATION_180;
} else if (orientation >= 250 && orientation < 290 && rotation != ROTATION_270) {
currentOrientation = Surface.ROTATION_270;
rotation = ROTATION_270;
}
if (prevOrientation != currentOrientation
&& orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
prevOrientation = currentOrientation;
if (currentOrientation != OrientationEventListener.ORIENTATION_UNKNOWN)
reportOrientationChanged(currentOrientation);
}
}
private void reportOrientationChanged(final int currentOrientation) {
int defaultOrientation = getDeviceDefaultOrientation();
int orthogonalOrientation = defaultOrientation == Configuration.ORIENTATION_LANDSCAPE ? Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int toReportOrientation;
if (currentOrientation == Surface.ROTATION_0
|| currentOrientation == Surface.ROTATION_180)
toReportOrientation = defaultOrientation;
else
toReportOrientation = orthogonalOrientation;
onSimpleOrientationChanged(toReportOrientation);
}
/**
* Must determine what is default device orientation (some tablets can have
* default landscape). Must be initialized when device orientation is
* defined.
*
* #return value of {#link Configuration#ORIENTATION_LANDSCAPE} or
* {#link Configuration#ORIENTATION_PORTRAIT}
*/
private int getDeviceDefaultOrientation() {
if (defaultScreenOrientation == CONFIGURATION_ORIENTATION_UNDEFINED) {
lock.lock();
defaultScreenOrientation = initDeviceDefaultOrientation(ctx);
lock.unlock();
}
return defaultScreenOrientation;
}
/**
* Provides device default orientation
*
* #return value of {#link Configuration#ORIENTATION_LANDSCAPE} or
* {#link Configuration#ORIENTATION_PORTRAIT}
*/
private int initDeviceDefaultOrientation(Context context) {
WindowManager windowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Configuration config = context.getResources().getConfiguration();
int rotation = windowManager.getDefaultDisplay().getRotation();
boolean isLand = config.orientation == Configuration.ORIENTATION_LANDSCAPE;
boolean isDefaultAxis = rotation == Surface.ROTATION_0
|| rotation == Surface.ROTATION_180;
int result = CONFIGURATION_ORIENTATION_UNDEFINED;
if ((isDefaultAxis && isLand) || (!isDefaultAxis && !isLand)) {
result = Configuration.ORIENTATION_LANDSCAPE;
} else {
result = Configuration.ORIENTATION_PORTRAIT;
}
return result;
}
/**
* Fires when orientation changes from landscape to portrait and vice versa.
*
* #param orientation
* value of {#link Configuration#ORIENTATION_LANDSCAPE} or
* {#link Configuration#ORIENTATION_PORTRAIT}
*/
public abstract void onSimpleOrientationChanged(int orientation);
}
I want to find the camera screen orientation in locked portrait orientation mode, well I am using camera in my fragment class and I have already set my screen orientation as portrait, but the problem I am facing is, when I turn my camera from portrait to landscape its getting changed and I need to set capture button visible only when the camera is in portrait mode. Can anyone help me to get the orientation changes in portrait mode?
Below is my code:
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
sensorManager.registerListener(new SensorEventListener() {
int orientation=-1;;
#Override
public void onSensorChanged(SensorEvent event) {
if (event.values[1] < 6.5 && event.values[1] > -6.5) {
if (orientation!=1) {
Log.d("Sensor", "Landscape");
}
orientation = 1;
} else {
if (orientation!=0) {
Log.d("Sensor", "Portrait");
}
orientation = 0;
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_GAME);
if (orientation == 0) {
// capture button visisble
} else {
// invisible
}
You can use OrientationEventListener for this. this is class that customise it.
public abstract class SimpleOrientationListener extends OrientationEventListener {
public static final int CONFIGURATION_ORIENTATION_UNDEFINED = Configuration.ORIENTATION_UNDEFINED;
private volatile int defaultScreenOrientation = CONFIGURATION_ORIENTATION_UNDEFINED;
public int prevOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
private Context ctx;
private ReentrantLock lock = new ReentrantLock(true);
public SimpleOrientationListener(Context context) {
super(context);
ctx = context;
}
public SimpleOrientationListener(Context context, int rate) {
super(context, rate);
ctx = context;
}
#Override
public void onOrientationChanged(final int orientation) {
int currentOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
if (orientation >= 330 || orientation < 30) {
currentOrientation = Surface.ROTATION_0;
} else if (orientation >= 60 && orientation < 120) {
currentOrientation = Surface.ROTATION_90;
} else if (orientation >= 150 && orientation < 210) {
currentOrientation = Surface.ROTATION_180;
} else if (orientation >= 240 && orientation < 300) {
currentOrientation = Surface.ROTATION_270;
}
if (prevOrientation != currentOrientation && orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
prevOrientation = currentOrientation;
if (currentOrientation != OrientationEventListener.ORIENTATION_UNKNOWN)
reportOrientationChanged(currentOrientation);
}
}
private void reportOrientationChanged(final int currentOrientation) {
int defaultOrientation = getDeviceDefaultOrientation();
int orthogonalOrientation = defaultOrientation == Configuration.ORIENTATION_LANDSCAPE ? Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
int toReportOrientation;
if (currentOrientation == Surface.ROTATION_0 || currentOrientation == Surface.ROTATION_180)
toReportOrientation = defaultOrientation;
else
toReportOrientation = orthogonalOrientation;
onSimpleOrientationChanged(toReportOrientation);
}
/**
* Must determine what is default device orientation (some tablets can have default landscape). Must be initialized when device orientation is defined.
*
* #return value of {#link Configuration#ORIENTATION_LANDSCAPE} or {#link Configuration#ORIENTATION_PORTRAIT}
*/
private int getDeviceDefaultOrientation() {
if (defaultScreenOrientation == CONFIGURATION_ORIENTATION_UNDEFINED) {
lock.lock();
defaultScreenOrientation = initDeviceDefaultOrientation(ctx);
lock.unlock();
}
return defaultScreenOrientation;
}
/**
* Provides device default orientation
*
* #return value of {#link Configuration#ORIENTATION_LANDSCAPE} or {#link Configuration#ORIENTATION_PORTRAIT}
*/
private int initDeviceDefaultOrientation(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Configuration config = context.getResources().getConfiguration();
int rotation = windowManager.getDefaultDisplay().getRotation();
boolean isLand = config.orientation == Configuration.ORIENTATION_LANDSCAPE;
boolean isDefaultAxis = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180;
int result = CONFIGURATION_ORIENTATION_UNDEFINED;
if ((isDefaultAxis && isLand) || (!isDefaultAxis && !isLand)) {
result = Configuration.ORIENTATION_LANDSCAPE;
} else {
result = Configuration.ORIENTATION_PORTRAIT;
}
return result;
}
/**
* Fires when orientation changes from landscape to portrait and vice versa.
*
* #param orientation value of {#link Configuration#ORIENTATION_LANDSCAPE} or {#link Configuration#ORIENTATION_PORTRAIT}
*/
public abstract void onSimpleOrientationChanged(int orientation);
}
Then where you want to detect orientation just call
SimpleOrientationListener mOrientationListener = new SimpleOrientationListener(
context) {
#Override
public void onSimpleOrientationChanged(int orientation) {
if(orientation == Configuration.ORIENTATION_LANDSCAPE){
}else if(orientation == Configuration.ORIENTATION_PORTRAIT){
}
}
};
mOrientationListener.enable();
You gonna have to use:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
//do your stuff with the button
}
}
If you want your activity not to be recreated during orientation then use
android:configChanges="orientation|keyboardHidden|screenSize"
If you want your activity to be forced to stay portrait then you gonna have to use
in your manifest file in the activity you would like just try android:screenOrientation="portrait"
Hope it helps!!!
You can achieve the orientation change value, when your activity set to only Portrait or Landscape specific and you want to perform some action when orientation changes.
private OrientationEventListener orientationEventListener;
initialize this variable in your class and implement it's listener in onCreate
orientationEventListener = new OrientationEventListener(this) {
#Override
public void onOrientationChanged(int orientation) {
Log.d("Orientation", orientation + " - " + currentOrientation);
if (orientation >= 330 || orientation < 30) {
currentOrientation = Surface.ROTATION_0;
} else if (orientation >= 60 && orientation < 120) {
currentOrientation = Surface.ROTATION_90;
} else if (orientation >= 150 && orientation < 210) {
currentOrientation = Surface.ROTATION_180;
} else if (orientation >= 240 && orientation < 300) {
currentOrientation = Surface.ROTATION_270;
}
}
};
currentOrentation is integer value for later use in the activity other units.
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!