Is there any way to "forward" scroll events from one scrolling view to my bottom sheet, so that my bottom sheet begins to expand when I over-scroll the first scrolling view?
Consider this tiny app:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int peekHeight = getResources().getDimensionPixelSize(R.dimen.bottom_sheet_peek_height); // 96dp
View bottomSheet = findViewById(R.id.bottomSheet);
BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(peekHeight);
}
}
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- LinearLayout holding children to scroll through -->
</android.support.v4.widget.NestedScrollView>
<View
android:id="#+id/bottomSheet"
android:layout_width="300dp"
android:layout_height="400dp"
android:layout_gravity="center_horizontal"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>
</android.support.design.widget.CoordinatorLayout>
Out of the box, this works just fine. I see 96dp worth of my bottom sheet, and I can swipe it up and down as normal. Additionally, I can see my scrolling content, and I can scroll it up and down as normal.
Let's assume I'm at the state shown in the second image. My NestedScrollView is scrolled all the way to the bottom and my bottom sheet is collapsed. I'd like to be able to swipe upwards on the NestedScrollView (not on the bottom sheet) and, since it can't scroll any farther, have that swipe gesture instead be sent to the bottom sheet, so that it begins to expand. Basically, have the app behave as though my gesture had been performed on the bottom sheet, not the scroll view.
My first thought was to look at NestedScrollView.OnScrollChangeListener, but I couldn't get that to work since it stops being triggered at the boundaries of the scrolling content (after all, it listens for scroll changes, and nothing's changing when you're at the edges).
I also took a look at creating my own subclass of BottomSheetBehavior and trying to override onInterceptTouchEvent(), but ran into trouble in two places. First, I only want to capture events when the sibling scroll view is at the bottom, and I could do that, but I was now capturing all events (making it impossible to scroll the sibling back up). Second, the private field mIgnoreEvents inside BottomSheetBehavior was blocking the bottom sheet from actually expanding. I can use reflection to access this field and prevent it from blocking me, but that feels evil.
Edit: I spent some more time looking into AppBarLayout.ScrollingViewBehavior, since that seemed to be pretty close to what I wanted (it converts swipes on one view into resizing on another), but that appears to manually set the offset pixel by pixel, and bottom sheets don't quite behave that way.
This is an update with a more general solution. It now handles being hidden and "skip collapsed" of the standard bottom view behavior.
The following solution uses a custom BottomSheetBehavior. Here is a quick video of a small app based upon your posted app with the custom behavior in place:
MyBottomSheetBehavior extends BottomSheetBehavior and does the heavy lifting for the desired behavior. MyBottomSheetBehavior is passive until the NestedScrollView reaches its bottom scroll limit. onNestedScroll() identifies that the limit has been reached and offsets the bottom sheet by the amount of the scroll until the offset for the fully expanded bottom sheet is reached. This is the expansion logic.
Once the bottom sheet is released from the bottom, the bottom sheet is considered "captured" until the user lifts a finger from the screen. While the bottom sheet is captured, onNestPreScroll() handles moving the bottom sheet toward the bottom of the screen. This is the collapsing logic.
BottomSheetBehavior doesn't provide a means to manipulate the bottom sheet other than to completely collapse or expand it. Other functionality that is needed is locked up in package-private functions of the base behavior. To get around this, I created a new class called BottomSheetBehaviorAccessors that shares a package (android.support.design.widget) with the stock behavior. This class provides access to some package-private methods that are used in the new behavior.
MyBottomSheetBehavior also accommodates the callbacks of BottomSheetBehavior.BottomSheetCallback and other general functionality.
MyBottomSheetBehavior.java
public class MyBottomSheetBehavior<V extends View> extends BottomSheetBehaviorAccessors<V> {
// The bottom sheet that interests us.
private View mBottomSheet;
// Offset when sheet is expanded.
private int mMinOffset;
// Offset when sheet is collapsed.
private int mMaxOffset;
// This is the bottom of the bottom sheet's parent.
private int mParentBottom;
// True if the bottom sheet is being moved through nested scrolls from NestedScrollView.
private boolean mSheetCaptured = false;
// True if the bottom sheet is touched directly and being dragged.
private boolean mIsheetTouched = false;
// Set to true on ACTION_DOWN on the NestedScrollView
private boolean mScrollStarted = false;
#SuppressWarnings("unused")
public MyBottomSheetBehavior() {
}
#SuppressWarnings("unused")
public MyBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
mSheetCaptured = false;
mIsheetTouched = parent.isPointInChildBounds(child, (int) ev.getX(), (int) ev.getY());
mScrollStarted = !mIsheetTouched;
}
return super.onInterceptTouchEvent(parent, child, ev);
}
#Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
mMinOffset = Math.max(0, parent.getHeight() - child.getHeight());
mMaxOffset = Math.max(parent.getHeight() - getPeekHeight(), mMinOffset);
mBottomSheet = child;
mParentBottom = parent.getBottom();
return super.onLayoutChild(parent, child, layoutDirection);
}
#Override
public void onNestedPreScroll(#NonNull CoordinatorLayout coordinatorLayout,
#NonNull V child, #NonNull View target, int dx, int dy,
#NonNull int[] consumed, int type) {
if (dy >= 0 || !mSheetCaptured || type != ViewCompat.TYPE_TOUCH
|| !(target instanceof NestedScrollView)) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
return;
}
// Pointer moving downward (dy < 0: scrolling toward top of data)
if (child.getTop() - dy <= mMaxOffset) {
// Dragging...
ViewCompat.offsetTopAndBottom(child, -dy);
setStateInternalAccessor(STATE_DRAGGING);
consumed[1] = dy;
} else if (isHideable()) {
// Hide...
ViewCompat.offsetTopAndBottom(child, Math.min(-dy, mParentBottom - child.getTop()));
consumed[1] = dy;
} else if (mMaxOffset - child.getTop() > 0) {
// Collapsed...
ViewCompat.offsetTopAndBottom(child, mMaxOffset - child.getTop());
consumed[1] = dy;
}
if (consumed[1] != 0) {
dispatchOnSlideAccessor(child.getTop());
}
}
#Override
public void onNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull V child,
#NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int type) {
if (dyUnconsumed <= 0 || !(target instanceof NestedScrollView)
|| type != ViewCompat.TYPE_TOUCH || getState() == STATE_HIDDEN) {
mSheetCaptured = false;
} else if (!mSheetCaptured) {
// Capture the bottom sheet only if it is at its collapsed height.
mSheetCaptured = isSheetCollapsed();
}
if (!mSheetCaptured) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
return;
}
/*
If the pointer is moving upward (dyUnconsumed > 0) and the scroll view isn't
consuming scroll (dyConsumed == 0) then the scroll view must be at the end
of its scroll.
*/
if (child.getTop() - dyUnconsumed < mMinOffset) {
// Expanded...
ViewCompat.offsetTopAndBottom(child, mMinOffset - child.getTop());
} else {
// Dragging...
ViewCompat.offsetTopAndBottom(child, -dyUnconsumed);
setStateInternalAccessor(STATE_DRAGGING);
}
dispatchOnSlideAccessor(child.getTop());
}
#Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
if (mScrollStarted) {
// Ignore initial call to this method before anything has happened.
mScrollStarted = false;
} else if (!mIsheetTouched) {
snapBottomSheet();
}
super.onStopNestedScroll(coordinatorLayout, child, target);
}
private void snapBottomSheet() {
if ((mMaxOffset - mBottomSheet.getTop()) > (mMaxOffset - mMinOffset) / 2) {
setState(BottomSheetBehavior.STATE_EXPANDED);
} else if (shouldHideAccessor(mBottomSheet, 0)) {
setState(BottomSheetBehavior.STATE_HIDDEN);
} else {
setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
private boolean isSheetCollapsed() {
return mBottomSheet.getTop() == mMaxOffset;
}
#SuppressWarnings("unused")
private static final String TAG = "MyBottomSheetBehavior";
}
BottomSheetBehaviorAccessors
package android.support.design.widget; // important!
// A "friend" class to provide access to some package-private methods in `BottomSheetBehavior`.
public class BottomSheetBehaviorAccessors<V extends View> extends BottomSheetBehavior<V> {
#SuppressWarnings("unused")
protected BottomSheetBehaviorAccessors() {
}
#SuppressWarnings("unused")
public BottomSheetBehaviorAccessors(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void setStateInternalAccessor(int state) {
super.setStateInternal(state);
}
protected void dispatchOnSlideAccessor(int top) {
super.dispatchOnSlide(top);
}
protected boolean shouldHideAccessor(View child, float yvel) {
return mHideable && super.shouldHide(child, yvel);
}
#SuppressWarnings("unused")
private static final String TAG = "BehaviorAccessor";
}
MainActivity.java
public class MainActivity extends AppCompatActivity{
private View mBottomSheet;
MyBottomSheetBehavior<View> mBehavior;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
int peekHeight = getResources().getDimensionPixelSize(R.dimen.bottom_sheet_peek_height); // 96dp
mBottomSheet = findViewById(R.id.bottomSheet);
mBehavior = (MyBottomSheetBehavior) MyBottomSheetBehavior.from(mBottomSheet);
mBehavior.setPeekHeight(peekHeight);
}
}
activity_main.xml
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stateListAnimator="#null"
android:theme="#style/AppTheme.AppBarOverlay"
app:expanded="false"
app:layout_behavior="android.support.design.widget.AppBarLayout$Behavior">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:statusBarScrim="?attr/colorPrimaryDark">
<ImageView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginTop="?attr/actionBarSize"
android:scaleType="centerCrop"
android:src="#drawable/seascape1"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="1.0"
tools:ignore="ContentDescription" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<com.example.bottomsheetoverscroll.MyNestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_blue_light" />
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_red_light" />
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_blue_light" />
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_red_light" />
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_blue_light" />
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_red_light" />
<View
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#android:color/holo_green_light" />
</LinearLayout>
</com.example.bottomsheetoverscroll.MyNestedScrollView>
<TextView
android:id="#+id/bottomSheet"
android:layout_width="300dp"
android:layout_height="400dp"
android:layout_gravity="center_horizontal"
android:background="#android:color/white"
android:text="Bottom Sheet"
android:textAlignment="center"
android:textSize="24sp"
android:textStyle="bold"
app:layout_behavior="com.example.bottomsheetoverscroll.MyBottomSheetBehavior" />
<!--app:layout_behavior="android.support.design.widget.BottomSheetBehavior" />-->
</android.support.design.widget.CoordinatorLayout>
Related
I am trying to reconcile the following two things:
A) I want a precise, uniform, and clean UI with several identically sized buttons that correspond exactly to the underlying 'grid cells' -- A UI that will look as similar as possible (proportionally to screen size) across as many Android devices as possible.
B) On Android, the screen dimensions (aspect ratio and actual pixel numbers) of the user's device are unknown (to the app) until runtime.
My solution to this was to: (there is a code example below!)
1) Lock the app to portrait mode,
2) Do not define anything in static/absolute terms like dp,px, etc. and instead conceptualize a 'basic unit of measure' that is a function of screen height -- 0.08% in my case -- and base everything off of that.
3) Set horizontal guidelines within a ConstraintLayout whose positions are expressed as a percentage of parent (screen) height.
4) Make all buttons use this 'basic unit' as their height and width by setting their XML layout_constraintDimensionRatio attribute to "1:1" and using the guidelines above (see step 3),
5) Accomplish positioning and dimensions of all views by using constraints to either these guidelines, the parent's bounds, or one additional vertical guideline at 50% of screen width.
The problem is that depending on the pixel height of the screen (whether it happens to be odd or even... or maybe other factors), the dimensions of a view/button, (and thus the paths drawn inside it) constrained between one pair of guidelines does not exactly match those of another view drawn between some other pair... even though the distance between both pairs of guidelines should be the same percentage of parent height. :)
Here is an example showing the Nexus 4 emulator:
At first I thought the problem was simply due to rounding 'error' during Android's dimension calculations, but then why would the view not be square even though they are prescribed the 1:1 ratio attribute?
The only solutions I can think of would be:
A) To do the layout programatically instead of with XML... and set the guideline positions as exact pixel locations instead of percentages, and answer the question, "what is 0.08 x screen height?" myself... making the appropriate corrections to compensate for 'indivisible' screen heights.
B) Override onLayout() in the custom views and "force" their dimensions to be consistent... but then this would defeat the purpose of guidelines. :(
But I'm really hoping there is an easier solution than A or B.
(I know someone is going to suggest GridLayout, but it's not an option, for a few reasons... one of which is that in GridLayout, views inside cells must be set to wrap_content... which means the paths they draw cannot be generated relative to parent at runtime).
Thanks for any other suggestions, though.
Code Example:
I whipped up a simple 'minimal example' below that should be easy to reconstruct in Android Studio. The logs will reveal the issue if it's not immediately apparent.
XML layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.08" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.92" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.38" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.46" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.54" />
<android.support.constraint.Guideline
android:id="#+id/guidelineHorizontalCenter4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.62" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonTopLeft"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonTopLeft"
app:layout_constraintBottom_toTopOf="#+id/guidelineHorizontalTop"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonTopRight"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonTopRight"
app:layout_constraintBottom_toTopOf="#+id/guidelineHorizontalTop"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonBottomLeft"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonBottomLeft"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/guidelineHorizontalBottom" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonBottomRight"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonBottomRight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/guidelineHorizontalBottom" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonMiddle"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonMiddle"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontalCenter3"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontalCenter2" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonMiddleTopLeft"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonMiddleTopLeft"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontalCenter2"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="#id/buttonMiddle"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontalCenter1" />
<com.example.boober.stack_aliasingproblem.CustomButton
android:id="#+id/buttonMiddleTopRight"
android:layout_width="0dp"
android:layout_height="0dp"
android:tag="buttonMiddleTopRight"
app:layout_constraintBottom_toBottomOf="#id/guidelineHorizontalCenter2"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintStart_toEndOf="#id/buttonMiddle"
app:layout_constraintTop_toTopOf="#id/guidelineHorizontalCenter1" />
</android.support.constraint.ConstraintLayout>
MainActivity.java:
public class MainActivity extends AppCompatActivity {
CustomButton buttonTopLeft;
CustomButton buttonTopRight;
CustomButton buttonMiddle;
CustomButton buttonMiddleTopLeft;
CustomButton getButtonMiddleTopRight;
CustomButton buttonBottomLeft;
CustomButton buttonBottomRight;
CustomButton[] arrayOfCustomButtons;
ConstraintLayout rootView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonTopLeft = findViewById(R.id.buttonTopLeft);
buttonTopRight = findViewById(R.id.buttonTopRight);
buttonBottomLeft = findViewById(R.id.buttonBottomLeft);
buttonBottomRight = findViewById(R.id.buttonBottomRight);
buttonMiddle = findViewById(R.id.buttonMiddle);
buttonMiddleTopLeft = findViewById(R.id.buttonMiddleTopLeft);
getButtonMiddleTopRight = findViewById(R.id.buttonMiddleTopRight);
arrayOfCustomButtons = new CustomButton[]{buttonTopLeft, buttonTopRight, buttonBottomLeft,
buttonBottomRight, buttonMiddle, buttonMiddleTopLeft, getButtonMiddleTopRight};
rootView = findViewById(R.id.rootView);
for (final CustomButton cb : arrayOfCustomButtons) {
cb.setClickable(true);
cb.post(new Runnable() {
#Override
public void run() {
Log.i("XXX", "width of: " + cb.getTag() + " is: "
+ cb.getMeasuredWidth());
}
});
}
rootView.post(new Runnable() {
#Override
public void run() {
Log.i("XXX", "height of rootView is: " + rootView.getMeasuredHeight());
}
});
}
}
CustomButton.java:
public class CustomButton extends View {
Path myOutlinePath;
Paint myThinPaintBrush;
Paint myThickPaintBrush;
boolean isHighlighted = false;
public CustomButton(Context context) {
super(context);
init();
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
float measuredWidth = getMeasuredWidth();
Log.i("XXX", "measured WIDTH Of " + this.getTag() + " is: " + measuredWidth);
Log.i("XXX", "measured HEIGT Of " + this.getTag() + " is: " + getMeasuredHeight());
Log.i("XXX", "\n ");
generateMyOutline(measuredWidth);
myThinPaintBrush.setStrokeWidth(measuredWidth/12);
myThickPaintBrush.setStrokeWidth(measuredWidth/6);
}
private void generateMyOutline(float W) {
Path path = new Path();
path.moveTo(0,0);
path.lineTo(W, 0);
path.lineTo(W, W);
path.lineTo(0, W);
path.lineTo(0,0);
myOutlinePath = path;
}
private void init() {
myOutlinePath = new Path();
myThinPaintBrush = new Paint();
myThinPaintBrush.setAntiAlias(false); // setting this to true does not solve the problem.
myThinPaintBrush.setStyle(Paint.Style.STROKE);
myThinPaintBrush.setStrokeCap(Paint.Cap.ROUND);
myThickPaintBrush = new Paint();
myThickPaintBrush.setAntiAlias(false);
myThickPaintBrush.setStyle(Paint.Style.STROKE);
myThickPaintBrush.setStrokeCap(Paint.Cap.ROUND);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (this.isClickable()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isHighlighted = true;
invalidate();
break;
case MotionEvent.ACTION_UP:
isHighlighted = false;
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
isHighlighted = false;
invalidate();
break;
}
}
return super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(myOutlinePath, myThinPaintBrush);
if (isHighlighted) {
canvas.drawPath(myOutlinePath, myThickPaintBrush);
}
super.onDraw(canvas);
}
}
I would go for the middle ground: Use your XML layout as is and make adjustments programmatically to the guideline positions. The following code converts percentage guidelines to fixed position guidelines by computing a new layout height that is a multiple of 8% of the height of the initial layout.
All sizes are computed correctly except for the bottom squares that tend to be larger. This can be easily corrected based upon your actual requirements (more important to be at the bottom or a certain distance from the other squares, for instance.)
MainActivity.jav
public class MainActivity extends AppCompatActivity {
CustomButton buttonTopLeft;
CustomButton buttonTopRight;
CustomButton buttonMiddle;
CustomButton buttonMiddleTopLeft;
CustomButton getButtonMiddleTopRight;
CustomButton buttonBottomLeft;
CustomButton buttonBottomRight;
CustomButton[] arrayOfCustomButtons;
ConstraintLayout rootView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonTopLeft = findViewById(R.id.buttonTopLeft);
buttonTopRight = findViewById(R.id.buttonTopRight);
buttonBottomLeft = findViewById(R.id.buttonBottomLeft);
buttonBottomRight = findViewById(R.id.buttonBottomRight);
buttonMiddle = findViewById(R.id.buttonMiddle);
buttonMiddleTopLeft = findViewById(R.id.buttonMiddleTopLeft);
getButtonMiddleTopRight = findViewById(R.id.buttonMiddleTopRight);
rootView = findViewById(R.id.rootView);
rootView.post(new Runnable() {
#Override
public void run() {
int rootViewHeight = rootView.getMeasuredHeight();
Log.i("XXX", "height of rootView is: " + rootViewHeight);
int segHeight = (int) (rootViewHeight * 0.08f);
adjustGuideline(R.id.guidelineHorizontalTop, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter1, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter2, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter3, segHeight);
adjustGuideline(R.id.guidelineHorizontalCenter4, segHeight);
adjustGuideline(R.id.guidelineHorizontalBottom, segHeight);
arrayOfCustomButtons = new CustomButton[]{buttonTopLeft, buttonTopRight, buttonBottomLeft,
buttonBottomRight, buttonMiddle, buttonMiddleTopLeft, getButtonMiddleTopRight};
rootView = findViewById(R.id.rootView);
for (final CustomButton cb : arrayOfCustomButtons) {
cb.setClickable(true);
cb.post(new Runnable() {
#Override
public void run() {
Log.i("MainActivity", "<<<< width of: " + cb.getTag() + " is: "
+ cb.getMeasuredWidth());
}
});
}
}
});
}
private void adjustGuideline(int guideLineId, int segHeight) {
Guideline gl = (Guideline) findViewById(guideLineId);
ConstraintLayout.LayoutParams lp = ((ConstraintLayout.LayoutParams) gl.getLayoutParams());
gl.setGuidelineBegin((int) (segHeight * lp.guidePercent / 0.08f));
gl.setGuidelinePercent(-1f);
}
}
In my app, I send a volley request which fetches list items one-by-one, not all at once. I want to implement a progressbar at the end of the recyclerview when the data is being fetched. The class 'updateAdapter' updates the adapter and I was thinking of making the progress bar visible in the recyclerview scroll listener. But I have no idea how to do this in my code.
updateAdapter.java
//THIS CLASS UPDATES THE RECYCLER VIEW
public class updateAdapter{
private FetchWpApi fetch;
private int totalItemCount;
private int prevLastvisible=0;
private int fpage=1;
private Context context;
private RecyclerView recyclerView;
private ArrayList<sItem> list= new ArrayList<>();
private LinearLayoutManager manager;
private ProgressBar progressBar;
//CONSTRUCTOR FOR updateAdapter
public updateAdapter(RecyclerView recyclerView, final Context context, String url, LinearLayoutManager manager){
this.context=context;
this.recyclerView=recyclerView;
fetch=new FetchWpApi(url,context);
this.manager=manager;
}
public void fetchAndPut()
{
if(recyclerView.getAdapter()==null) {
fetch.fetchApiData(fpage, new FetchWpApi.Callback() {
#Override
public void onSuccess(sItem sitem) {
list.add(sitem);
if (list.size() == 1 || recyclerView.getAdapter() == null) {
ItemAdapter adapter = new ItemAdapter(context, list);
recyclerView.setAdapter(adapter);
} else if (list.size() > 1 && recyclerView.getAdapter() != null) {
recyclerView.getAdapter().notifyDataSetChanged();
recyclerView.getAdapter().notifyItemRangeChanged(0, recyclerView.getAdapter().getItemCount());
}
}
#Override
public void onFail(String msg) {
Toast.makeText(context, "FAILED PRIMARY LOAD", Toast.LENGTH_LONG).show();
}
});
}
recyclerView.addOnItemTouchListener(
//Not important
);
//SCROLL LISTENER ATTACHED TO THE RECYCLER VIEW
//SHOW PROGRESS BAR HERE?
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(final RecyclerView recyclerView, int dx, int dy)
{
if(dy > 0) //check for scroll down
{
totalItemCount = manager.getItemCount();
int lastVisibleItemPosition = ((LinearLayoutManager) manager).findLastVisibleItemPosition();
if( (lastVisibleItemPosition+1)==totalItemCount && totalItemCount%10==0 && lastVisibleItemPosition>prevLastvisible)
{
//SET VISIBILITY OF THE PROGRESS BAR IN THE LAST CARD ITEM TO VISIBLE ??
fpage++;
//loading = false;
Log.v("...", "Last Item !");
//Do pagination.. i.e. fetch new data
prevLastvisible=lastVisibleItemPosition;
fetch.fetchApiData(fpage,new FetchWpApi.Callback(){
#Override
public void onSuccess(sItem sitem){
list.add(sitem);
recyclerView.getAdapter().notifyDataSetChanged();
recyclerView.getAdapter().notifyItemRangeChanged(0, recyclerView.getAdapter().getItemCount());
}
#Override
public void onFail(String msg){
Toast.makeText(context,"FAILED ONLOAD",Toast.LENGTH_LONG).show();
}
});
}
}
}
});
}
}
The progressbar which I want to display is in this cardview:
finalcard.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/cvItem"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<!--<LinearLayout-->
<!--android:id="#+id/linearlayout"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!-->-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="#+id/tvTitle"
android:textColor="#000"
android:text="Sample text Sampletext sample text sample text sample text"
android:layout_alignParentStart="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="#+id/ivMainImage"
android:layout_marginTop="15dp"
android:paddingStart="16dp"
android:paddingEnd="2dp"
android:maxLines="4"
android:textSize="20sp"
android:layout_gravity="start"
/>
<TextView
android:id="#+id/dateText"
android:text="DATE"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="0dp"
android:layout_gravity="start"
android:layout_below="#id/tvTitle"
android:ellipsize="marquee"/>
<ImageView
android:id="#+id/ivMainImage"
android:layout_width="140dp"
android:layout_height="130dp"
android:layout_gravity="end"
android:layout_alignParentEnd="true"
android:layout_margin="15dp"
android:src="#android:drawable/btn_star"
android:scaleType="centerCrop"
/>
<View
android:id="#+id/view"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#0C000000"
android:layout_below="#id/ivMainImage"
/>
<ProgressBar
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/progressBar2"
android:layout_below="#id/view"
android:visibility="gone"
/>
</RelativeLayout>
Fix your progress bar below your recycler view instead of card view.
Set visible progress bar when call web service, and set visibility Gone after complete service call
//Progress bar setVisibility - Visible
progressbar.setVisibility(View.VISIBLE);
fetch.fetchApiData(fpage, new FetchWpApi.Callback() {
#Override
public void onSuccess(sItem sitem) {
progressbar.setVisibility(View.GONE);
list.add(sitem);
recyclerView.getAdapter().notifyDataSetChanged();
recyclerView.getAdapter().notifyItemRangeChanged(0, recyclerView.getAdapter().getItemCount());
}
#Override
public void onFail(String msg) {
progressbar.setVisibility(View.GONE);
Toast.makeText(context, "FAILED ONLOAD", Toast.LENGTH_LONG).show();
}
});
I think you can follow this link to add header or footer Recyclerview offically. And you add footer as progressbar to load more data, and remove footer when you finish loading data.
There are 2 ways:
1. Put the progress bar in the bottom of the screen and when you are bottom of the RecyclerView by int last = layoutManager.findLastVisibleItemPosition(); it just display and when you get the response then hide it;
2. In your adapter:
add a dummy row to the last position of your list.
check in adapter when the position is last then show the progress bar:
if(position == list.size - 1) {
// create a layout which only have progress bar
View row = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.progressbar, viewGroup, false);
} else {
View row = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.layout_list, viewGroup, false);
}
Problem scenario:
I have a view pager in which I have divided a form in different tabs.
Now one of the tabs looks like:
The XML for one row being:
<LinearLayout
android:id="#+id/email_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:tag="lin_email_container">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="#drawable/rounded_linear"
android:tag="email_sub_container"
android:weightSum="5">
<Spinner
android:id="#+id/email_spinner"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.5" />
<View
android:id="#+id/view3"
android:layout_width="0.3dp"
android:layout_height="match_parent"
android:background="#D3D3D3" />
<EditText
android:id="#+id/edittext_email"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:background="#null"
android:hint="Email"
android:inputType="textEmailAddress"
android:paddingLeft="10dp"
android:textSize="14sp" />
<ImageView
android:id="#+id/btn_emailadd"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="right"
android:layout_weight=".5"
android:src="#drawable/ic_action_add_to_queue" />
</LinearLayout>
</LinearLayout>
On press of the + icon at the extreme right, which is an ImageView with id = btn_emailadd I add one more similar control, its like the good old add more field and looks like this after added:
As is visible above, I added one email field.
The Problem
In order to get values from these dynamic EditTexts I need to implement a textwatcher for all. I am also implementing tags for views, but I am afraid I am not able to maintain unique tags while adding content. This is how I add content:
public void addView(LinearLayout container, String[] spin_array, String hint) {
LayoutInflater layoutInflater = (LayoutInflater) getActivity().getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View addView = layoutInflater.inflate(R.layout.dynamic_content, null);
Spinner spin_dynamic = (Spinner) addView.findViewById(R.id.email_spinner1);
EditText edt_dynamic = (EditText) addView.findViewById(R.id.edittext_email1);
ImageView remove_dynamic = (ImageView) addView.findViewById(R.id.btn_remove);
LinearLayout sub_layout = (LinearLayout) addView.findViewById(R.id.sub_layout);
edt_dynamic.setHint(hint);
setUpSpinners(spin_array, spin_dynamic);
edt_dynamic.setTag(container.getTag() + "edt_text" + container.getChildCount());
container.indexOfChild(edt_dynamic);
// get height from dimens
int height = (int) getResources().getDimension(R.dimen.lin_height);
// set this height
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, height);
// we are only concerned about top margin here.
layoutParams.setMargins(0, (int) getResources().getDimension(R.dimen.topMargin), 0, 0);
container.addView(addView, 1, layoutParams);
remove_dynamic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((LinearLayout) addView.getParent()).removeView(addView);
}
});
}
So it melts down to, without having any solid Id's and without any solig tags, how can I implement textwatchers, that too to a dynamic content.
The easiest solution I could find to this problem is to use a Parent traversal mechanism, however this would not work in a View Pager, as there is no event I can run this code on:
public void traverseLinearLayout(LinearLayout layout) {
for (int i = 0; i < layout.getChildCount(); i++) {
View v = layout.getChildAt(i);
if (v instanceof LinearLayout) {
traverseLinearLayout((LinearLayout) v);
}
if (v instanceof EditText) {
System.out.println(v.getTag());
//Get Edit Text value here
}
if (v instanceof Spinner) {
}
}
}
I see that you have tags on the Parent Layout:
android:id="#+id/email_container"
This solves half of your problem. Now make a generic Class which implements a TextWatcher:
public class GenericTextWatcher implements TextWatcher {
public View view;
public GenericTextWatcher(View view) {
this.view = view;
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//do nothing
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
//do nothing
}
public void afterTextChanged(Editable s) {
System.out.println(view.getTag());
}
}
When you create the view dynamically, you just need to know if the EditText is for email or for any other form field like: web, phone, messenger etc. You can use .contains or .equals methods for the same.
This is a bit hacky but now you can easily do something like:
edt_dynamic.setTag(container.getTag() + "edt_text" + new Date());
edt_dynamic.addTextChangedListener(new GenericTextWatcher(edt_dynamic));
Beware to replace new Date() with something having a nanosecond precision.
In the Textwatcher, you can add key value pairs to a Map, depending on the scenario. Make sure you remove the Key, Value (from the Map) when X for removing EditText is pressed.
I`ve been looking for a way to enable the MPAndroidChart to only display the value(label) of data point when clicked. But It seems that I could not find it online even in the documentation.
I used the line chart and what I want is to only display the label of the certain point when clicked.
1- Enable touch in the chart
chart.setTouchEnabled(true);
2 - Create MarkerView
public class CustomMarkerView extends MarkerView {
private TextView tvContent;
public CustomMarkerView (Context context, int layoutResource) {
super(context, layoutResource);
// this markerview only displays a textview
tvContent = (TextView) findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
#Override
public void refreshContent(Entry e, Highlight highlight) {
tvContent.setText("" + e.getVal()); // set the entry-value as the display text
}
#Override
public int getXOffset() {
// this will center the marker-view horizontally
return -(getWidth() / 2);
}
#Override
public int getYOffset() {
// this will cause the marker-view to be above the selected value
return -getHeight();
}
}
3 - Create the tvContent view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="#drawable/markerImage" >
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="7dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text=""
android:textSize="12dp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
4. Set the view Marker in the chart
CustomMarkerView mv = new CustomMarkerView (Context, R.layout.custom_marker_view_layout);
chart.setMarkerView(mv);
https://github.com/PhilJay/MPAndroidChart/wiki/MarkerView
Use IMarker Interface (MarkerView has been deprecated since release 3.0.0)
1. Create a new class that implements the IMarker interface
public class YourMarkerView extends MarkerView {
private TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
// find your layout components
tvContent = (TextView) findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
#Override
public void refreshContent(Entry e, Highlight highlight) {
tvContent.setText("" + e.getY());
// this will perform necessary layouting
super.refreshContent(e, highlight);
}
private MPPointF mOffset;
#Override
public MPPointF getOffset() {
if(mOffset == null) {
// center the marker horizontally and vertically
mOffset = new MPPointF(-(getWidth() / 2), -getHeight());
}
return mOffset;
}}
2. set your marker to the chart
IMarker marker = new YourMarkerView();
chart.setMarker(marker);
Reference: https://github.com/PhilJay/MPAndroidChart/wiki/IMarker-Interface
Example in Kotlin for future you:
Define marker layout i.e. gold_marker_layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="6828.1465"
android:textSize="12sp"
android:textColor="#android:color/white"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Quick note: I set the initial text to 6828.1465 because that's my max-width text - watch out here.
If you don't need getXOffset and getYOffset you can assign marker like that:
lineChart.marker = object : MarkerView(context, R.layout.gold_marker_layout) {
override fun refreshContent(e: Entry, highlight: Highlight) {
(findViewById<View>(R.id.tvContent) as TextView).text = "${e.y}"
}
}
That's it, you should be good to go
I have an custom view that is extended from MultiAutoCompleteTextView to create chiped view like contacts in gmail. when i a contact to this view and the keyborad is dismissed it casuse stack overflow. it happens only in my nexus 4 this is the logcat.
java.lang.StackOverflowError
at android.text.DynamicLayout.reflow(DynamicLayout.java:284)
at android.text.DynamicLayout.<init>(DynamicLayout.java:170)
at android.widget.TextView.makeSingleLayout(TextView.java:6134)
at android.widget.TextView.makeNewLayout(TextView.java:6032)
at android.widget.TextView.checkForRelayout(TextView.java:6571)
at android.widget.TextView.onRtlPropertiesChanged(TextView.java:8672)
at android.view.View.resolvePadding(View.java:12407)
at android.view.View.getPaddingLeft(View.java:15603)
at com.tokenautocomplete.TokenCompleteTextView.maxTextWidth(TokenCompleteTextView.java:260)
at com.tokenautocomplete.TokenCompleteTextView.access$1000(TokenCompleteTextView.java:54)
at com.tokenautocomplete.TokenCompleteTextView$ViewSpan.prepView(TokenCompleteTextView.java:822)
at com.tokenautocomplete.TokenCompleteTextView$ViewSpan.getSize(TokenCompleteTextView.java:841)
at com.tokenautocomplete.TokenCompleteTextView$TokenImageSpan.getSize(TokenCompleteTextView.java:885)
this is my prep view code
private void prepView() {
int widthSpec = MeasureSpec.makeMeasureSpec((int)maxTextWidth(), MeasureSpec.AT_MOST);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
view.measure(widthSpec, heightSpec);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
this is line 260 of tokenCompleteTextView
private float maxTextWidth() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
is use TokenAutoComplete libray for the token view.
this is my layout
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#color/gray"
android:clickable="true">
<LinearLayout
android:id="#+id/llsearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="horizontal"
android:weightSum="4"
android:background="#color/listview_color"
android:layout_marginTop="#dimen/hdpi_4dp"
android:layout_marginBottom="#dimen/hdpi_4dp"
android:gravity="center_vertical">
<in.ispg.chipview.ConatctCompleteTextView
android:id="#+id/edtsearch"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="#string/search"
android:layout_weight="1"
android:textSize="#dimen/textsize_edittext"
android:textColor="#color/black"
android:paddingLeft="#dimen/hdpi_4dp"
android:paddingRight="#dimen/hdpi_4dp"
android:layout_marginLeft="#dimen/hdpi_8dp"
android:layout_marginRight="#dimen/hdpi_8dp"
android:singleLine="false"
android:minLines="1"
android:maxLines="5"
>
<requestFocus />
</in.ispg.chipview.ConatctCompleteTextView>
<Button
android:id="#+id/btnsearch"
android:layout_width="fill_parent"
android:layout_height="#dimen/hdpi_33dp"
android:text="#string/done"
android:layout_weight="3"
android:background="#drawable/send_button"
android:layout_marginRight="#dimen/hdpi_8dp"
android:textColor="#color/white"
android:gravity="center"
/>
<in.ispg.utils.FontTextView
android:id="#android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textSize="#dimen/textsize_edittext"
android:textColor="#595959"
android:textStyle="bold"
android:text="" />
<ProgressBar
android:id="#+id/progressBar1"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:indeterminate="true"
android:indeterminateDrawable ="#drawable/progress"
android:visibility="gone" />
Note
I know how to dismiss the keyboard. that is not my problem. I get a stackoverflow error when i do so in a specific view.
I use this code to hide soft keyboard
// To close the soft keyboard
InputMethodManager inputMethod = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethod.hideSoftInputFromWindow(getView().getWindowToken(), 0);
1.) try this , hope it will solve your issue, i use the below code to HideSoftkeyboard
public void hideSoftKeyboard(Activity activity) {
InputMethodManager inputMethodManager = (InputMethodManager) activity
.getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus()
.getWindowToken(), 0);
}
OR
If I need to care about when the keyboard appears and disappears (which is quite often) then what I do is customize my top-level layout class into one which overrides onMeasure(). The basic logic is that if the layout finds itself filling significantly less than the total area of the window, then a soft keyboard is probably showing.
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;
/*
* LinearLayoutThatDetectsSoftKeyboard - a variant of LinearLayout that can detect when
* the soft keyboard is shown and hidden (something Android can't tell you, weirdly).
*/
public class LinearLayoutThatDetectsSoftKeyboard extends LinearLayout {
public LinearLayoutThatDetectsSoftKeyboard(Context context, AttributeSet attrs) {
super(context, attrs);
}
public interface Listener {
public void onSoftKeyboardShown(boolean isShowing);
}
private Listener listener;
public void setListener(Listener listener) {
this.listener = listener;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
Activity activity = (Activity)getContext();
Rect rect = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rect.top;
int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
int diff = (screenHeight - statusBarHeight) - height;
if (listener != null) {
listener.onSoftKeyboardShown(diff>128); // assume all soft keyboards are at least 128 pixels high
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Then in your Activity class...
public class MyActivity extends Activity implements LinearLayoutThatDetectsSoftKeyboard.Listener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
LinearLayoutThatDetectsSoftKeyboard mainLayout = (LinearLayoutThatDetectsSoftKeyboard)findViewById(R.id.main);
mainLayout.setListener(this);
...
}
#Override
public void onSoftKeyboardShown(boolean isShowing) {
// do whatever you need to do here
}
...
}
I had the same problem. I don't know why it happens. I tested in differents devices and in a emulator. This error happens just in some devices and in emulator I can't simulate it. This is what I did:
#Override
public int getSize(Paint paint, CharSequence charSequence, int i, int i2, Paint.FontMetricsInt fm) {
try{
prepView();
}catch(StackOverflowError error){
}
...
This code prevent the application close.