I would like to know how to apply or emulate foreground effect in a view different from FrameLayout, as LinearLayout or RelativeLayout
This is what I have now:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/row_background"
android:foreground="#drawable/foreground_row">
...
</FrameLayout>
And I want something like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/row_background"
app:foreground="#drawable/foreground_row">
...
</RelativeLayout>
Thanks in advance!!
The idea is to surround your layout with a FrameLayout, and set the selector and the onClick event to this layout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/selectableItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foreground="#drawable/foreground_row"
>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/row_background">
...
</RelativeLayout>
</FrameLayout>
You can find a full explanation at my blog:
http://antonioleiva.com/unveiling-bandhook-foreground-any-layout/
Or you can extend rhis FRelativeLayout https://gist.github.com/shakalaca/6199283
Checkout ForegroundView library with Gradle integration. It supports following views
ForegroundImageView
ForegroundButton
ForegroundTextView
ForegroundImageButton
ForegroundEditText
ForegroundWebView
ForegroundLinearLayout
ForegroundRelativeLayout
ForegroundGridLayout
ForegroundGridView
ForegroundHorizontalScrollView
ForegroundListView
ForegroundScrollViewForegroundImageView
Here is a possible implementation, one which also supports checking a layout.
A nearly identical solution can be applied to any layout view you wish (only the CTOR is different).
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private boolean mChecked;
private static final String TAG = CheckableLinearLayout.class.getCanonicalName();
private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
private Drawable mForeground;
public CheckableLinearLayout(final Context context) {
this(context, null, 0);
}
public CheckableLinearLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckableLinearLayout(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.CheckableLinearLayout, defStyle, 0);
setForegroundEx(a.getDrawable(R.styleable.CheckableLinearLayout_foreground));
a.recycle();
}
public void setForegroundEx(final Drawable drawable) {
this.mForeground = drawable;
}
#Override
protected int[] onCreateDrawableState(final int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
#Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final Drawable drawable = getBackground();
boolean needRedraw = false;
final int[] myDrawableState = getDrawableState();
if (drawable != null) {
drawable.setState(myDrawableState);
needRedraw = true;
}
if (mForeground != null) {
mForeground.setState(myDrawableState);
needRedraw = true;
}
if (needRedraw)
invalidate();
}
#Override
protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) {
super.onSizeChanged(width, height, oldwidth, oldheight);
if (mForeground != null)
mForeground.setBounds(0, 0, width, height);
}
#Override
protected void dispatchDraw(final Canvas canvas) {
super.dispatchDraw(canvas);
if (mForeground != null)
mForeground.draw(canvas);
}
#Override
public boolean isChecked() {
return mChecked;
}
#Override
public void setChecked(final boolean checked) {
mChecked = checked;
refreshDrawableState();
//TODO think if you wish to also check inner views, maybe even recursively
}
#Override
public void toggle() {
setChecked(!mChecked);
}
#Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.checked = isChecked();
return savedState;
}
#Override
public void onRestoreInstanceState(final Parcelable state) {
final SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
setChecked(savedState.checked);
requestLayout();
}
#SuppressLint("ClickableViewAccessibility")
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public boolean onTouchEvent(final MotionEvent e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && //
e.getActionMasked() == MotionEvent.ACTION_DOWN && //
mForeground != null)
mForeground.setHotspot(e.getX(), e.getY());
return super.onTouchEvent(e);
}
// /////////////
// SavedState //
// /////////////
private static class SavedState extends BaseSavedState {
boolean checked;
SavedState(final Parcelable superState) {
super(superState);
}
private SavedState(final Parcel in) {
super(in);
checked = (Boolean) in.readValue(null);
}
#Override
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
#Override
public String toString() {
return TAG + ".SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked
+ "}";
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
#Override
public SavedState createFromParcel(final Parcel in) {
return new SavedState(in);
}
#Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
}
}
attr.xml
<declare-styleable name="CheckableLinearLayout">
<attr name="foreground"/>
</declare-styleable>
Here's a more minimal way to do it, using Kotlin, and without the part of being checkable :
class LinearLayoutEx #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : LinearLayout(context, attrs, defStyle) {
var foregroundDrawable: Drawable? = null
set(value) {
field = value
invalidate()
}
init {
val a = context
.obtainStyledAttributes(attrs, R.styleable.LinearLayoutEx, defStyle, 0)
foregroundDrawable = a.getDrawable(R.styleable.LinearLayoutEx_foreground)
a.recycle()
}
override fun drawableStateChanged() {
super.drawableStateChanged()
val drawable = background
var needRedraw = false
val myDrawableState = drawableState
if (drawable != null) {
drawable.state = myDrawableState
needRedraw = true
}
if (foregroundDrawable != null) {
foregroundDrawable!!.state = myDrawableState
needRedraw = true
}
if (needRedraw)
invalidate()
}
override fun onSizeChanged(width: Int, height: Int, oldwidth: Int, oldheight: Int) {
super.onSizeChanged(width, height, oldwidth, oldheight)
foregroundDrawable?.setBounds(0, 0, width, height)
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
foregroundDrawable?.draw(canvas)
}
#SuppressLint("ClickableViewAccessibility")
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onTouchEvent(e: MotionEvent): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && e.actionMasked == MotionEvent.ACTION_DOWN)
foregroundDrawable?.setHotspot(e.x, e.y)
return super.onTouchEvent(e)
}
}
attr.xml
<declare-styleable name="LinearLayoutEx" tools:ignore="MissingDefaultResource">
<attr name="foreground"/>
</declare-styleable>
Related
In this below code i want to set center_horizontal as attachmentSlidingLayer view id but layout_gravity and gravity for parent and child view doesn't work correctly and by default gravity is left side
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:slidingLayer="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<io.codetail.widget.RevealFrameLayout <!-- extends from FrameLayout -->
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal">
<com.test.libraries.SlidingLayer <!-- extends from BoundedFrameLayout-->
android:id="#+id/attachmentSlidingLayer"
android:layout_width="match_parent"
android:layout_height="210dp"
android:layout_gravity="center_horizontal"
android:elevation="5dp"
app:bounded_width="450dp"
android:foregroundGravity="center_horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:background="#drawable/sheet_shadow"
android:orientation="vertical"
app:bounded_width="450dp">
</LinearLayout>
</<com.test.libraries.SlidingLayer.SlidingLayer >
</io.codetail.widget.RevealFrameLayout>
</android.support.design.widget.CoordinatorLayout>
BoundedFrameLayout class:
public class BoundedFrameLayout extends FrameLayout {
private final int mBoundedWidth;
private final int mBoundedHeight;
public BoundedFrameLayout(Context context) {
super(context);
mBoundedWidth = 0;
mBoundedHeight = 0;
}
public BoundedFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BoundedView);
mBoundedWidth = a.getDimensionPixelSize(R.styleable.BoundedView_bounded_width, 0);
mBoundedHeight = a.getDimensionPixelSize(R.styleable.BoundedView_bounded_height, 0);
a.recycle();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Adjust width as necessary
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mBoundedWidth > 0 && mBoundedWidth < measuredWidth) {
int measureMode = MeasureSpec.getMode(widthMeasureSpec);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(mBoundedWidth, measureMode);
}
// Adjust height as necessary
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
if (mBoundedHeight > 0 && mBoundedHeight < measuredHeight) {
int measureMode = MeasureSpec.getMode(heightMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mBoundedHeight, measureMode);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
RevealFrameLayout class:
public class RevealFrameLayout extends FrameLayout implements RevealAnimator{
private Path mRevealPath;
private final Rect mTargetBounds = new Rect();
private RevealInfo mRevealInfo;
private boolean mRunning;
private float mRadius;
public RevealFrameLayout(Context context) {
this(context, null);
}
public RevealFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RevealFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mRevealPath = new Path();
}
#Override
public void onRevealAnimationStart() {
mRunning = true;
}
#Override
public void onRevealAnimationEnd() {
mRunning = false;
invalidate(mTargetBounds);
}
#Override
public void onRevealAnimationCancel() {
onRevealAnimationEnd();
}
#Override
public void setRevealRadius(float radius){
mRadius = radius;
mRevealInfo.getTarget().getHitRect(mTargetBounds);
invalidate(mTargetBounds);
}
#Override
public float getRevealRadius(){
return mRadius;
}
#Override
public void attachRevealInfo(RevealInfo info) {
mRevealInfo = info;
}
#Override
public SupportAnimator startReverseAnimation() {
if(mRevealInfo != null && mRevealInfo.hasTarget() && !mRunning) {
return ViewAnimationUtils.createCircularReveal(mRevealInfo.getTarget(),
mRevealInfo.centerX, mRevealInfo.centerY,
mRevealInfo.endRadius, mRevealInfo.startRadius);
}
return null;
}
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if(mRunning && child == mRevealInfo.getTarget()){
final int state = canvas.save();
mRevealPath.reset();
mRevealPath.addCircle(mRevealInfo.centerX, mRevealInfo.centerY, mRadius, Path.Direction.CW);
canvas.clipPath(mRevealPath);
boolean isInvalided = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(state);
return isInvalided;
}
return super.drawChild(canvas, child, drawingTime);
}
}
I would like to know how to apply or emulate foreground effect in a view different from FrameLayout, as LinearLayout or RelativeLayout
This is what I have now:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/row_background"
android:foreground="#drawable/foreground_row">
...
</FrameLayout>
And I want something like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/row_background"
app:foreground="#drawable/foreground_row">
...
</RelativeLayout>
Thanks in advance!!
The idea is to surround your layout with a FrameLayout, and set the selector and the onClick event to this layout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/selectableItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foreground="#drawable/foreground_row"
>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/row_background">
...
</RelativeLayout>
</FrameLayout>
You can find a full explanation at my blog:
http://antonioleiva.com/unveiling-bandhook-foreground-any-layout/
Or you can extend rhis FRelativeLayout https://gist.github.com/shakalaca/6199283
Checkout ForegroundView library with Gradle integration. It supports following views
ForegroundImageView
ForegroundButton
ForegroundTextView
ForegroundImageButton
ForegroundEditText
ForegroundWebView
ForegroundLinearLayout
ForegroundRelativeLayout
ForegroundGridLayout
ForegroundGridView
ForegroundHorizontalScrollView
ForegroundListView
ForegroundScrollViewForegroundImageView
Here is a possible implementation, one which also supports checking a layout.
A nearly identical solution can be applied to any layout view you wish (only the CTOR is different).
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private boolean mChecked;
private static final String TAG = CheckableLinearLayout.class.getCanonicalName();
private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked };
private Drawable mForeground;
public CheckableLinearLayout(final Context context) {
this(context, null, 0);
}
public CheckableLinearLayout(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckableLinearLayout(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
final TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.CheckableLinearLayout, defStyle, 0);
setForegroundEx(a.getDrawable(R.styleable.CheckableLinearLayout_foreground));
a.recycle();
}
public void setForegroundEx(final Drawable drawable) {
this.mForeground = drawable;
}
#Override
protected int[] onCreateDrawableState(final int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
#Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final Drawable drawable = getBackground();
boolean needRedraw = false;
final int[] myDrawableState = getDrawableState();
if (drawable != null) {
drawable.setState(myDrawableState);
needRedraw = true;
}
if (mForeground != null) {
mForeground.setState(myDrawableState);
needRedraw = true;
}
if (needRedraw)
invalidate();
}
#Override
protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) {
super.onSizeChanged(width, height, oldwidth, oldheight);
if (mForeground != null)
mForeground.setBounds(0, 0, width, height);
}
#Override
protected void dispatchDraw(final Canvas canvas) {
super.dispatchDraw(canvas);
if (mForeground != null)
mForeground.draw(canvas);
}
#Override
public boolean isChecked() {
return mChecked;
}
#Override
public void setChecked(final boolean checked) {
mChecked = checked;
refreshDrawableState();
//TODO think if you wish to also check inner views, maybe even recursively
}
#Override
public void toggle() {
setChecked(!mChecked);
}
#Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
final Parcelable superState = super.onSaveInstanceState();
final SavedState savedState = new SavedState(superState);
savedState.checked = isChecked();
return savedState;
}
#Override
public void onRestoreInstanceState(final Parcelable state) {
final SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
setChecked(savedState.checked);
requestLayout();
}
#SuppressLint("ClickableViewAccessibility")
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public boolean onTouchEvent(final MotionEvent e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && //
e.getActionMasked() == MotionEvent.ACTION_DOWN && //
mForeground != null)
mForeground.setHotspot(e.getX(), e.getY());
return super.onTouchEvent(e);
}
// /////////////
// SavedState //
// /////////////
private static class SavedState extends BaseSavedState {
boolean checked;
SavedState(final Parcelable superState) {
super(superState);
}
private SavedState(final Parcel in) {
super(in);
checked = (Boolean) in.readValue(null);
}
#Override
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
#Override
public String toString() {
return TAG + ".SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked
+ "}";
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
#Override
public SavedState createFromParcel(final Parcel in) {
return new SavedState(in);
}
#Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
}
}
attr.xml
<declare-styleable name="CheckableLinearLayout">
<attr name="foreground"/>
</declare-styleable>
Here's a more minimal way to do it, using Kotlin, and without the part of being checkable :
class LinearLayoutEx #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : LinearLayout(context, attrs, defStyle) {
var foregroundDrawable: Drawable? = null
set(value) {
field = value
invalidate()
}
init {
val a = context
.obtainStyledAttributes(attrs, R.styleable.LinearLayoutEx, defStyle, 0)
foregroundDrawable = a.getDrawable(R.styleable.LinearLayoutEx_foreground)
a.recycle()
}
override fun drawableStateChanged() {
super.drawableStateChanged()
val drawable = background
var needRedraw = false
val myDrawableState = drawableState
if (drawable != null) {
drawable.state = myDrawableState
needRedraw = true
}
if (foregroundDrawable != null) {
foregroundDrawable!!.state = myDrawableState
needRedraw = true
}
if (needRedraw)
invalidate()
}
override fun onSizeChanged(width: Int, height: Int, oldwidth: Int, oldheight: Int) {
super.onSizeChanged(width, height, oldwidth, oldheight)
foregroundDrawable?.setBounds(0, 0, width, height)
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
foregroundDrawable?.draw(canvas)
}
#SuppressLint("ClickableViewAccessibility")
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun onTouchEvent(e: MotionEvent): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && e.actionMasked == MotionEvent.ACTION_DOWN)
foregroundDrawable?.setHotspot(e.x, e.y)
return super.onTouchEvent(e)
}
}
attr.xml
<declare-styleable name="LinearLayoutEx" tools:ignore="MissingDefaultResource">
<attr name="foreground"/>
</declare-styleable>
How can i set the default MenuItem for the official BottomNavigationView (com.android.support:design:25.0.1)?
If I call programmatically menuItem.setCheckable(true).setChecked(true) the zoom effect is not performed and the BottomNavigationView shows like this:
There is more simpler way to do this since Android Support Library 25.3.0 :
bottomNavigationView.setSelectedItemId(R.id.id_of_item_you_want_to_select_as_default);
I achieved this in a much simpler way:
//R.id.bottom_bar_icon is the icon you would like clicked by default
bottomNavigationView.getMenu().performIdentifierAction(R.id.bottom_bar_icon, 0);
//set the corresponding menu item to checked = true
//and the other items to checked = false
bottomNavigationView.getMenu().getItem(0).setChecked(false);
bottomNavigationView.getMenu().getItem(1).setChecked(true);
bottomNavigationView.getMenu().getItem(2).setChecked(false);
In the end I was able to achieve this issue extending the BottomNavigationView like this:
public class RichBottomNavigationView extends BottomNavigationView {
private ViewGroup mBottomItemsHolder;
private int mLastSelection = INVALID_POSITION;
private Drawable mShadowDrawable;
private boolean mShadowVisible = true;
private int mWidth;
private int mHeight;
private int mShadowElevation = 2;
public RichBottomNavigationView(Context context) {
super(context);
init();
}
public RichBottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public RichBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
;
}
private void init() {
mShadowDrawable = ContextCompat.getDrawable(getContext(), R.drawable.shadow);
if (mShadowDrawable != null) {
mShadowDrawable.setCallback(this);
}
setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));
setShadowVisible(true);
setWillNotDraw(false);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h + mShadowElevation, oldw, oldh);
mWidth = w;
mHeight = h;
updateShadowBounds();
}
private void updateShadowBounds() {
if (mShadowDrawable != null && mBottomItemsHolder != null) {
mShadowDrawable.setBounds(0, 0, mWidth, mShadowElevation);
}
ViewCompat.postInvalidateOnAnimation(this);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mShadowDrawable != null && mShadowVisible) {
mShadowDrawable.draw(canvas);
}
}
public void setShadowVisible(boolean shadowVisible) {
setWillNotDraw(!mShadowVisible);
updateShadowBounds();
}
public int getShadowElevation() {
return mShadowVisible ? mShadowElevation : 0;
}
public int getSelectedItem() {
return mLastSelection = findSelectedItem();
}
#CallSuper
public void setSelectedItem(int position) {
if (position >= getMenu().size() || position < 0) return;
View menuItemView = getMenuItemView(position);
if (menuItemView == null) return;
MenuItemImpl itemData = ((MenuView.ItemView) menuItemView).getItemData();
itemData.setChecked(true);
boolean previousHapticFeedbackEnabled = menuItemView.isHapticFeedbackEnabled();
menuItemView.setSoundEffectsEnabled(false);
menuItemView.setHapticFeedbackEnabled(false); //avoid hearing click sounds, disable haptic and restore settings later of that view
menuItemView.performClick();
menuItemView.setHapticFeedbackEnabled(previousHapticFeedbackEnabled);
menuItemView.setSoundEffectsEnabled(true);
mLastSelection = position;
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
BottomNavigationState state = new BottomNavigationState(superState);
mLastSelection = getSelectedItem();
state.lastSelection = mLastSelection;
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof BottomNavigationState)) {
super.onRestoreInstanceState(state);
return;
}
BottomNavigationState bottomNavigationState = (BottomNavigationState) state;
mLastSelection = bottomNavigationState.lastSelection;
dispatchRestoredState();
super.onRestoreInstanceState(bottomNavigationState.getSuperState());
}
private void dispatchRestoredState() {
if (mLastSelection != 0) { //Since the first item is always selected by the default implementation, dont waste time
setSelectedItem(mLastSelection);
}
}
private View getMenuItemView(int position) {
View bottomItem = mBottomItemsHolder.getChildAt(position);
if (bottomItem instanceof MenuView.ItemView) {
return bottomItem;
}
return null;
}
private int findSelectedItem() {
int itemCount = getMenu().size();
for (int i = 0; i < itemCount; i++) {
View bottomItem = mBottomItemsHolder.getChildAt(i);
if (bottomItem instanceof MenuView.ItemView) {
MenuItemImpl itemData = ((MenuView.ItemView) bottomItem).getItemData();
if (itemData.isChecked()) return i;
}
}
return INVALID_POSITION;
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
mBottomItemsHolder = (ViewGroup) getChildAt(0);
updateShadowBounds();
//This sucks.
MarginLayoutParams layoutParams = (MarginLayoutParams) mBottomItemsHolder.getLayoutParams();
layoutParams.topMargin = (mShadowElevation + 2) / 2;
}
static class BottomNavigationState extends BaseSavedState {
public int lastSelection;
#RequiresApi(api = Build.VERSION_CODES.N)
public BottomNavigationState(Parcel in, ClassLoader loader) {
super(in, loader);
lastSelection = in.readInt();
}
public BottomNavigationState(Parcelable superState) {
super(superState);
}
#Override
public void writeToParcel(#NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(lastSelection);
}
public static final Parcelable.Creator<NavigationView.SavedState> CREATOR
= ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<NavigationView.SavedState>() {
#Override
public NavigationView.SavedState createFromParcel(Parcel parcel, ClassLoader loader) {
return new NavigationView.SavedState(parcel, loader);
}
#Override
public NavigationView.SavedState[] newArray(int size) {
return new NavigationView.SavedState[size];
}
});
}
}
and calling setSelectedItem(2)
I am using PreferenceActivity for the setting of my app.
I want to add a new preference that allow the user to select an icon.
For this task I want to use a ListPreference, but I want also to show the icon in the list.
I tried to customize the ListPreference to use a custom layout, but the problem is that once I do that the list items are not clickable (it does show my custom layout and use the default value for the current selection).
I tested it on different emulator version and on Galaxy S2. When pressing the item I could see some effect of the pressed/unpressed, but the onClick method is not called.
I followed the instruction on Android: Checkable Linear Layout for adding custom layout (I also tried the option describe in How to customize list preference radio button, but the same result).
IconTypePreference.java (copied from ListPreference and modified):
public class IconTypePreference extends DialogPreference {
private IconType value;
private int clickedDialogIndex;
private boolean valueSet;
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public IconTypePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public IconTypePreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public IconTypePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public IconTypePreference(Context context) {
super(context);
}
public void setValue(String value) {
// Always persist/notify the first time.
final boolean changed = !TextUtils.equals(getValueText(), value);
if (changed || !valueSet) {
if (value == null) {
this.value = null;
} else {
this.value = IconType.valueOf(value);
}
valueSet = true;
persistString(value);
if (changed) {
notifyChanged();
}
}
}
public void setValueIndex(int index) {
setValue(IconType.values()[index].toString());
}
public IconType getValue() {
return value;
}
public String getValueText() {
return (value == null ? null : value.toString());
}
public int findIndexOfValue(String value) {
IconType[] values = IconType.values();
for (int i = values.length - 1; i >= 0; i--) {
if (values[i].toString().equals(value)) {
return i;
}
}
return -1;
}
private int getValueIndex() {
return findIndexOfValue(getValueText());
}
#Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
clickedDialogIndex = getValueIndex();
builder.setSingleChoiceItems(new IconTypeAdapter(getContext()), clickedDialogIndex,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
clickedDialogIndex = which;
IconTypePreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
dialog.dismiss();
}
});
/*
* The typical interaction for list-based dialogs is to have
* click-on-an-item dismiss the dialog instead of the user having to
* press 'Ok'.
*/
builder.setPositiveButton(null, null);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult && clickedDialogIndex >= 0) {
String value = IconType.values()[clickedDialogIndex].toString();
if (callChangeListener(value)) {
setValue(value);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setValue(restoreValue ? getPersistedString(getValueText()) : (String)defaultValue);
}
#Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
// No need to save instance state since it's persistent
return superState;
}
final SavedState myState = new SavedState(superState);
myState.value = getValueText();
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState)state;
super.onRestoreInstanceState(myState.getSuperState());
setValue(myState.value);
}
private static class SavedState extends BaseSavedState {
String value;
public SavedState(Parcel source) {
super(source);
value = source.readString();
}
#Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(value);
}
public SavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
private static class IconTypeAdapter extends ArrayAdapter<IconType> {
private final String[] iconTypeText;
private LayoutInflater inflater;
public IconTypeAdapter(Context context) {
super(context, R.layout.icon_type_item, IconType.values());
this.inflater = LayoutInflater.from(context);
iconTypeText = context.getResources().getStringArray(R.array.icon_type);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.icon_type_item, parent, false);
}
((TextView)convertView.findViewById(R.id.text)).setText(iconTypeText[position]);
convertView.setClickable(true);
// todo: set view text
return convertView;
}
#Override
public boolean hasStableIds() {
return true;
}
#Override
public long getItemId(int position) {
return position;
}
}
}
CheckableLinearLayout.java
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private Checkable checkable;
public CheckableLinearLayout(Context context) {
super(context);
}
public CheckableLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
checkable = getCheckable(this);
if (checkable == null) {
throw new RuntimeException("Missing Checkable component");
}
}
private Checkable getCheckable(ViewGroup viewGroup) {
View v;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; ++i) {
v = getChildAt(i);
if (v instanceof Checkable) {
return (Checkable)v;
} else if (v instanceof ViewGroup) {
Checkable result = getCheckable((ViewGroup)v);
if (result != null) {
return result;
}
}
}
return null;
}
#Override
public void setChecked(boolean checked) {
checkable.setChecked(checked);
}
#Override
public boolean isChecked() {
return checkable.isChecked();
}
#Override
public void toggle() {
checkable.toggle();
}
}
icon_type_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.utils.ui.widget.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView android:id="#+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:focusable="false"
android:focusableInTouchMode="false"/>
<RadioButton android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"/>
</com.utils.ui.widget.CheckableLinearLayout>
Added to settings.xml
<com.utils.ui.preference.IconTypePreference
android:key="icon_type"
android:defaultValue="type_b"
android:title="#string/icon_type_preference_title"/>
EDIT
There is a bug in CheckableLinearLayout.java
Replace the getCheckable method with this:
private Checkable getCheckable(ViewGroup viewGroup) {
View v;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; ++i) {
v = viewGroup.getChildAt(i);
if (v instanceof Checkable) {
return (Checkable)v;
} else if (v instanceof ViewGroup) {
Checkable result = getCheckable((ViewGroup)v);
if (result != null) {
return result;
}
}
}
return null;
}
Found the solution to the problem.
The problem was in the getView method of the adapter:
I changed
convertView.setClickable(true);
to
convertView.setClickable(false);
I have a weird problem, for some reason the android:ellipsize="end" works, but added the point in the middle of the text == centered vertically instead of being aligned to baseline:
I checked for any "center" properties, but there is none of those:
Update:
This is the XML part:
<com.citylifeapps.cups.customviews.CarmelaTextView
android:id="#+id/venue_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/venue_distance"
android:layout_toRightOf="#+id/venue_name"
android:gravity="left"
android:text="#string/placeholder_venue_address"
android:textColor="#color/cups_white"
android:textSize="20sp"
android:textStyle="bold"
android:ellipsize="end"
android:singleLine="true"
android:scrollHorizontally="true"
android:layout_alignBaseline="#+id/venue_name" />
And the custom TextView class:
public class CarmelaTextView extends TextView {
public CarmelaTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setCarmelaTypeface(context);
}
public CarmelaTextView(Context context) {
super(context);
setCarmelaTypeface(context);
}
private void setCarmelaTypeface(Context context) {
if (this.isInEditMode()) return;
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "carmela.ttf");
this.setTypeface(typeface);
}
}
further check shows that if I use a simple TextView the problem disappears,
but there is nothing in the custom TextView that will cause such a behavior.
Does anyone know why this might happen?
Thanks.
It looks like the problem lies within my custom font I'm using for this custom TextView, from the accepted answer here:
Why does TextView in single line elipsized with "end" show boxes?
I'm guessing that I'm facing the same problem but with a different result because the 3 dots (...) U+FEFF glyph for my font is different.
But still if some one found a solution that works for this issue I would be glad if he could share it.
I used this class to resolve this issue
public class EllipsizingTextView extends TextView {
private static final String ELLIPSIS = "...";
public interface EllipsizeListener {
void ellipsizeStateChanged(boolean ellipsized);
}
private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>();
private boolean isEllipsized;
private boolean isStale;
private boolean programmaticChange;
private String fullText;
private int maxLines = -1;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding = 0.0f;
public EllipsizingTextView(Context context) {
super(context);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 1));
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 1));
}
public void addEllipsizeListener(EllipsizeListener listener) {
if (listener == null) {
throw new NullPointerException();
}
ellipsizeListeners.add(listener);
}
public void removeEllipsizeListener(EllipsizeListener listener) {
ellipsizeListeners.remove(listener);
}
public boolean isEllipsized() {
return isEllipsized;
}
#Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
this.maxLines = maxLines;
isStale = true;
}
public int getMaxLines() {
return maxLines;
}
#Override
public void setLineSpacing(float add, float mult) {
this.lineAdditionalVerticalPadding = add;
this.lineSpacingMultiplier = mult;
super.setLineSpacing(add, mult);
}
#Override
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
super.onTextChanged(text, start, before, after);
if (!programmaticChange) {
fullText = text.toString();
isStale = true;
}
}
#Override
protected void onDraw(Canvas canvas) {
if (isStale) {
super.setEllipsize(null);
resetText();
}
super.onDraw(canvas);
}
private void resetText() {
int maxLines = getMaxLines();
String workingText = fullText;
boolean ellipsized = false;
if (maxLines != -1) {
Layout layout = createWorkingLayout(workingText);
if (layout.getLineCount() > maxLines) {
workingText = fullText.substring(0,
layout.getLineEnd(maxLines - 1)).trim();
while (createWorkingLayout(workingText + ELLIPSIS)
.getLineCount() > maxLines) {
workingText = workingText.substring(0,
workingText.length() - 1 - 1);
}
workingText = workingText + ELLIPSIS;
ellipsized = true;
}
}
if (!workingText.equals(getText())) {
programmaticChange = true;
try {
setText(workingText);
} finally {
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized) {
isEllipsized = ellipsized;
for (EllipsizeListener listener : ellipsizeListeners) {
listener.ellipsizeStateChanged(ellipsized);
}
}
}
private Layout createWorkingLayout(String workingText) {
return new StaticLayout(workingText, getPaint(), getWidth()
- getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_NORMAL,
lineSpacingMultiplier, lineAdditionalVerticalPadding, false);
}
#Override
public void setEllipsize(TruncateAt where) {
// Ellipsize settings are not respected
}
}