Related
I made a custom keyboard. When you long press a key, a PopupWindow shows some extra choices above the key. The problem is that in API 28, this popup gets clipped (or even completely hidden for the top row).
I had solved this problem for API < 28 with
popupWindow.setClippingEnabled(false);
However, with API 28 the problem has come back. Here is more of the code:
private void layoutAndShowPopupWindow(Key key, int xPosition) {
popupWindow = new PopupWindow(popupView,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
popupWindow.setClippingEnabled(false);
int location[] = new int[2];
key.getLocationInWindow(location);
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
popupView.measure(measureSpec, measureSpec);
int popupWidth = popupView.getMeasuredWidth();
int spaceAboveKey = key.getHeight() / 4;
int x = xPosition - popupWidth / popupView.getChildCount() / 2;
int screenWidth = getScreenWidth();
if (x < 0) {
x = 0;
} else if (x + popupWidth > screenWidth) {
x = screenWidth - popupWidth;
}
int y = location[1] - popupView.getMeasuredHeight() - spaceAboveKey;
popupWindow.showAtLocation(key, Gravity.NO_GRAVITY, x, y);
}
Did something happen to no longer allow third party keyboards to show content outside of the keyboard view? (This is how it is in iOS.)
What do I need to do to get the PopupWindow to stop being clipped?
Updated to show a more tailored approach.Updated to work with windowSoftInputMode="adjustResize".
It looks like clipping outside of windows may be a new fact of Android life although I have not found documentation to that effect. Regardless, the following method may be the preferred way to go and is, I believe, standard although not very well documented.
In the following, MyInputMethodService instantiates a keyboard that has eight keys on the bottom and an empty view strip above where popups are displayed for the top row of keys. When a key is pressed, the key value is shown in a popup window above the key for the duration of the key press. Since the empty view above the keys encloses the popups, clipping does not occur. (Not a very useful keyboard, but it makes the point.)
The button and "Low text" EditText are under the top view strip. Invocation of onComputeInsets() permits touches on the keyboard keys but disallows keyboard touches in the empty area covered by the inset. In this area, touches are passed down to the underlying views - here the "Low text" EditText and a Button that displays "OK!" when clicked.
"Gboard" seems to work in a similar fashion but uses a sister FrameLayout to display the popups with translation. Here is what a "4" popup looks like in the Layout Inspector for "Gboard".
MyInputMethodService
public class MyInputMethodService extends InputMethodService
implements View.OnTouchListener {
private View mTopKey;
private PopupWindow mPopupWindow;
private View mPopupView;
#Override
public View onCreateInputView() {
final ConstraintLayout keyboardView = (ConstraintLayout) getLayoutInflater().inflate(R.layout.keyboard, null);
mTopKey = keyboardView.findViewById(R.id.a);
mTopKey.setOnTouchListener(this);
keyboardView.findViewById(R.id.b).setOnTouchListener(this);
keyboardView.findViewById(R.id.c).setOnTouchListener(this);
keyboardView.findViewById(R.id.d).setOnTouchListener(this);
keyboardView.findViewById(R.id.e).setOnTouchListener(this);
keyboardView.findViewById(R.id.f).setOnTouchListener(this);
keyboardView.findViewById(R.id.g).setOnTouchListener(this);
keyboardView.findViewById(R.id.h).setOnTouchListener(this);
mPopupView = getLayoutInflater().inflate(R.layout.popup, keyboardView, false);
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mPopupView.measure(measureSpec, measureSpec);
mPopupWindow = new PopupWindow(mPopupView, ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
return keyboardView;
}
#Override
public void onComputeInsets(InputMethodService.Insets outInsets) {
// Do the standard stuff.
super.onComputeInsets(outInsets);
// Only the keyboard are with the keys is touchable. The rest should pass touches
// through to the views behind. contentTopInsets set to play nice with windowSoftInputMode
// defined in the manifest.
outInsets.visibleTopInsets = mTopKey.getTop();
outInsets.contentTopInsets = mTopKey.getTop();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
layoutAndShowPopupWindow((TextView) v);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mPopupWindow.dismiss();
break;
}
return true;
}
private void layoutAndShowPopupWindow(TextView key) {
((TextView) mPopupView.findViewById(R.id.popupKey)).setText(key.getText());
int x = key.getLeft() + (key.getWidth() - mPopupView.getMeasuredWidth()) / 2;
int y = key.getTop() - mPopupView.getMeasuredHeight();
mPopupWindow.showAtLocation(key, Gravity.NO_GRAVITY, x, y);
}
}
keyboard.xml
The View is defined solely to give the popups a place to expand into and has no other purpose.
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="#+id/a" />
<Button
android:id="#+id/a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="A"
app:layout_constraintBottom_toTopOf="#+id/e"
app:layout_constraintEnd_toStartOf="#+id/b"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="B"
app:layout_constraintBottom_toTopOf="#+id/f"
app:layout_constraintEnd_toStartOf="#+id/c"
app:layout_constraintStart_toEndOf="#+id/a" />
<Button
android:id="#+id/c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="C"
app:layout_constraintBottom_toTopOf="#+id/g"
app:layout_constraintEnd_toStartOf="#+id/d"
app:layout_constraintStart_toEndOf="#+id/b" />
<Button
android:id="#+id/d"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="D"
app:layout_constraintBottom_toTopOf="#+id/h"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/c" />
<Button
android:id="#+id/e"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="E"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/f"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/f"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="F"
app:layout_constraintEnd_toStartOf="#+id/g"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/e"
app:layout_constraintTop_toTopOf="#+id/e" />
<Button
android:id="#+id/g"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="G"
app:layout_constraintEnd_toStartOf="#+id/h"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/f"
app:layout_constraintTop_toTopOf="#+id/e" />
<Button
android:id="#+id/h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="H"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/g"
app:layout_constraintTop_toTopOf="#+id/g" />
</android.support.constraint.ConstraintLayout>
popup.xml
Just the popup.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="#android:color/black"
android:gravity="center"
android:orientation="vertical"
android:padding="3dp">
<TextView
android:id="#+id/popupKey"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="A"
android:textColor="#android:color/white" />
</LinearLayout>
activity_main
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="High text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="20dp"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="#+id/editText"
android:layout_width="133dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:ems="10"
android:inputType="textPersonName"
android:hint="Low text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/button" />
</android.support.constraint.ConstraintLayout>
A general idea to show popup views is to create them using WindowManager which has not limitations of PopupWindow.
I assume that the InputMethodService is responsible to show the popup view.
As showing such window needs to get overlay permission in API 23 and higher, we need to make a temp Activity to do this for us . The result of getting permission would be delivered to the InputMethodService using an EventBus event. You can check the overlay permission where you want according to architecture (for example every time the keyboard goes up).
Here is an implementation of this idea which may need some manipulations to work exactly you want. I hope it helps.
MyInputMethodService.java
import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.provider.Settings;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
public class MyInputMethodService extends InputMethodService {
private FloatViewManager mFloatViewManager;
#Override
public void onCreate() {
super.onCreate();
EventBus.getDefault().register(this);
checkDrawOverlayPermission();
}
#Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
private boolean checkDrawOverlayPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(this, CheckPermissionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return false;
} else {
return true;
}
}
private void showPopup(Key key, int xPosition){
mFloatViewManager = new FloatViewManager(this);
if (checkDrawOverlayPermission()) {
mFloatViewManager.showFloatView(key, xPosition);
}
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(CanDrawOverlaysEvent event) {
if (event.isAllowed()) {
mFloatViewManager.showFloatView(key, xPosition);
} else {
// Maybe show an error
}
}
}
FloatViewManager.java
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import static android.content.Context.WINDOW_SERVICE;
public class FloatViewManager {
private WindowManager mWindowManager;
private View mFloatView;
private WindowManager.LayoutParams mFloatViewLayoutParams;
#SuppressLint("InflateParams")
public FloatViewManager(Context context) {
mWindowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
LayoutInflater inflater = LayoutInflater.from(context);
mFloatView = inflater.inflate(R.layout.float_view_layout, null);
// --------- do initializations:
TextView textView = mFloatView.findViewById(R.id.textView);
// ...
// ---------
mFloatViewLayoutParams = new WindowManager.LayoutParams();
mFloatViewLayoutParams.format = PixelFormat.TRANSLUCENT;
mFloatViewLayoutParams.flags = WindowManager.LayoutParams.FORMAT_CHANGED;
mFloatViewLayoutParams.type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
: WindowManager.LayoutParams.TYPE_PHONE;
mFloatViewLayoutParams.gravity = Gravity.NO_GRAVITY;
mFloatViewLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mFloatViewLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
public void dismissFloatView() {
mWindowManager.removeViewImmediate(mFloatView);
}
public void showFloatView(Key key, int xPosition) {
// calculate x and y position as you did instead of 0
mFloatViewLayoutParams.x = 0;
mFloatViewLayoutParams.y = 0;
mWindowManager.addView(mFloatView, mFloatViewLayoutParams);
mWindowManager.updateViewLayout(mFloatView, mFloatViewLayoutParams);
}
}
CheckPermissionActivity.java
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import org.greenrobot.eventbus.EventBus;
public class CheckPermissionActivity extends AppCompatActivity {
private static final int REQUEST_CODE_DRAW_OVERLAY_PERMISSION = 5;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_DRAW_OVERLAY_PERMISSION);
} else {
finish();
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_DRAW_OVERLAY_PERMISSION) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {
EventBus.getDefault().post(new CanDrawOverlaysEvent(true));
} else {
EventBus.getDefault().post(new CanDrawOverlaysEvent(false));
}
finish();
}
}
}
CanDrawOverlaysEvent.java
public class CanDrawOverlaysEvent {
private boolean mIsAllowed;
public CanDrawOverlaysEvent(boolean isAllowed) {
mIsAllowed = isAllowed;
}
public boolean isAllowed() {
return mIsAllowed;
}
}
build.gradle
dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
}
I have fixed that with LatinIME(AOSP) like:
my input view layout xml file is
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.my.android.ime.InputView
android:id="#+id/input_view"
android:background="#color/black"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
copy function "updateSoftInputWindowLayoutParameters" from LatinIME.java
private void updateSoftInputWindowLayoutParameters() {
// Override layout parameters to expand {#link SoftInputWindow} to the entire screen.
// See {#link InputMethodService#setinputView(View)} and
// {#link SoftInputWindow#updateWidthHeight(WindowManager.LayoutParams)}.
final Window window = getWindow().getWindow();
ViewLayoutUtils.updateLayoutHeightOf(window, LayoutParams.MATCH_PARENT);
// This method may be called before {#link #setInputView(View)}.
if (mInputView != null) {
// In non-fullscreen mode, {#link InputView} and its parent inputArea should expand to
// the entire screen and be placed at the bottom of {#link SoftInputWindow}.
// In fullscreen mode, these shouldn't expand to the entire screen and should be
// coexistent with {#link #mExtractedArea} above.
// See {#link InputMethodService#setInputView(View) and
// com.android.internal.R.layout.input_method.xml.
final int layoutHeight = isFullscreenMode()
? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
final View inputArea = window.findViewById(android.R.id.inputArea);
ViewLayoutUtils.updateLayoutHeightOf(inputArea, layoutHeight);
ViewLayoutUtils.updateLayoutGravityOf(inputArea, Gravity.BOTTOM);
ViewLayoutUtils.updateLayoutHeightOf(mInputView, layoutHeight);
}
}
Overriding function: "updateFullscreenMode", "setInputView", "onComputeInsets" and copy code from LatinIME.java - finally modify the code like
private View mInputView;
private InsetsUpdater mInsetsUpdater;
...
#Override
public void onStartInputView(EditorInfo info, boolean restarting) {
...
updateFullscreenMode();
super.onStartInputView(info, restarting);
}
#Override
public void updateFullscreenMode() {
super.updateFullscreenMode();
updateSoftInputWindowLayoutParameters();
}
#Override
public void setInputView(final View view) {
super.setInputView(view);
mInputView = view;
mInsetsUpdater = ViewOutlineProviderCompatUtils.setInsetsOutlineProvider(view);
updateSoftInputWindowLayoutParameters();
//mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view);
//if (hasSuggestionStripView()) {
// mSuggestionStripView.setListener(this, view);
//}
}
#Override
public void onComputeInsets(final InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
// This method may be called before {#link #setInputView(View)}.
if (mInputView == null) {
return;
}
//final SettingsValues settingsValues = mSettings.getCurrent();
//final View visibleKeyboardView = mKeyboardSwitcher.getVisibleKeyboardView();
final View visibleKeyboardView = mInputView.findViewById(R.id.input_view);
//if (visibleKeyboardView == null || !hasSuggestionStripView()) {
// return;
//}
final int inputHeight = mInputView.getHeight();
//if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) {
// // If there is a hardware keyboard and a visible software keyboard view has been hidden,
// // no visual element will be shown on the screen.
// outInsets.contentTopInsets = inputHeight;
// outInsets.visibleTopInsets = inputHeight;
// mInsetsUpdater.setInsets(outInsets);
// return;
//}
//final int suggestionsHeight = (!mKeyboardSwitcher.isShowingEmojiPalettes()
// && mSuggestionStripView.getVisibility() == View.VISIBLE)
// ? mSuggestionStripView.getHeight() : 0;
final int visibleTopY = inputHeight - visibleKeyboardView.getHeight();// - suggestionsHeight;
//mSuggestionStripView.setMoreSuggestionsHeight(visibleTopY);
// Need to set expanded touchable region only if a keyboard view is being shown.
if (visibleKeyboardView.isShown()) {
final int touchLeft = 0;
//final int touchTop = mKeyboardSwitcher.isShowingMoreKeysPanel() ? 0 : visibleTopY;
final int touchTop = visibleTopY;
final int touchRight = visibleKeyboardView.getWidth();
final int touchBottom = inputHeight;
outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
outInsets.touchableRegion.set(touchLeft, touchTop, touchRight, touchBottom);
Log.i(TAG, "onComputeInsets: left=" + touchLeft + ", top=" + touchTop + ", right=" + touchRight + ", bottom=" + touchBottom);
}
Log.i(TAG, "onComputeInsets: visibleTopY=" + visibleTopY);
outInsets.contentTopInsets = visibleTopY;
outInsets.visibleTopInsets = visibleTopY;
mInsetsUpdater.setInsets(outInsets);
}
copy file "ViewLayoutUtils.java", "ViewOutlineProviderCompatUtils.java", "ViewOutlineProviderCompatUtilsLXX.java" from LatinIME(AOSP) package and modify package name
The simplest solution to this is to not attach popup window to keyboard decorview:
popupWindow.setAttachedInDecor(false);
i am continuously searching and trying to sort out a issue in custom view and Relative layout.
i found multiple solution but only few of solution are usefull,but they not fulfilling my requirement completely.
partial solution 1.
partial solution 2.
via these solution i can manage the height and width of my custom view, but i just want to align my custom view in center of parent RelativeLayout,
code which i am trying throw hit-and-run is below. ;)
JAVA CODE
public class MyActiveView extends View {
public Movie mMovie;
public long movieStart;
private int gifId;
public MyActiveView(Context context, AttributeSet attrs) {
super(context, attrs);
initializeView();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth / 2, parentHeight);
}
private void initializeView() {
InputStream is = getContext().getResources().openRawResource(R.raw.pj_logo1);
mMovie = Movie.decodeStream(is);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
super.onDraw(canvas);
long now = android.os.SystemClock.uptimeMillis();
if (movieStart == 0) {
movieStart = now;
}
if (mMovie != null) {
int relTime = (int) ((now - movieStart) % mMovie.duration());
mMovie.setTime(relTime);
mMovie.draw(canvas, getWidth() - mMovie.width(), getHeight() - mMovie.height());
this.invalidate();
}
}
public int getActiveResource() {
return this.gifId;
}
public void setActiveResource(int resId) {
this.gifId = resId;
initializeView();
}
}
XML CODE
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/backsourceImagesplash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/bg_blur" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#color/colorTransparentWhite" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="256dp"
android:layout_centerVertical="true"
android:gravity="centre">
<pj.com.pjlib.activity_base.support_class.MyActiveView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
i want to set my custom active view layout in centre of its parent Relative Layout, but stuked in it. any help will be appreciated.
check the alignment of both image,i am trying for attribute android:layout_centerInParent="true", according to Relative layout support the "congrats image" should be in center, but it's not happening.
so i am thinking,i missed something in my custom view class but what is that, i don't know.
Change android:gravity="centre" with android:gravity="center" in Relative Layout. I think this will helpful to you.
Actually I am developing an application in android and I want to show the total no. of unread emails on the Action Bar Menu Item (like flipkart and all other applications already have) using latest Appcompat V21. I tried a code which I have found here but its not working with AppCompat V21.
Please help me if you can!
Posting the same code here:
=> layout/menu/menu_actionbar.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
...
<item android:id="#+id/menu_hotlist"
android:actionLayout="#layout/action_bar_notifitcation_icon"
android:showAsAction="always"enter code here
android:icon="#drawable/ic_bell"
android:title="#string/hotlist" />
...
</menu>
=> layout/action_bar_notifitcation_icon.xml
Note style and android:clickable properties. these make the layout the size of a button and make the background gray when touched.
<?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="fill_parent"
android:orientation="vertical"
android:gravity="center"
android:layout_gravity="center"
android:clickable="true"
style="#android:style/Widget.ActionButton">
<ImageView
android:id="#+id/hotlist_bell"
android:src="#drawable/ic_bell"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_margin="0dp"
android:contentDescription="bell"
/>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/hotlist_hot"
android:layout_width="wrap_content"
android:minWidth="17sp"
android:textSize="12sp"
android:textColor="#ffffffff"
android:layout_height="wrap_content"
android:gravity="center"
android:text="#null"
android:layout_alignTop="#id/hotlist_bell"
android:layout_alignRight="#id/hotlist_bell"
android:layout_marginRight="0dp"
android:layout_marginTop="3dp"
android:paddingBottom="1dp"
android:paddingRight="4dp"
android:paddingLeft="4dp"
android:background="#drawable/rounded_square"/>
</RelativeLayout>
=> drawable-xhdpi/ic_bell.png
A 64x64 pixel image with 10 pixel wide paddings from all sides. You are supposed to have 8 pixel wide paddings, but I find most default items being slightly smaller than that. Of course, you'll want to use different sizes for different densities.
=> drawable/rounded_square.xml
Here, #ff222222 (color #222222 with alpha #ff (fully visible)) is the background color of my Action Bar.
**<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="#ffff0000" />
<stroke android:color="#ff222222" android:width="2dp"/>
</shape>**
=> com/ipp/MyAppAndroid/MyActivity.java
private int hot_number = 0;
private TextView ui_hot = null;
#Override public boolean onCreateOptionsMenu(final Menu menu) {
MenuInflater menuInflater = getSupportMenuInflater();
menuInflater.inflate(R.menu.menu_actionbar, menu);
final View menu_hotlist = menu.findItem(R.id.menu_hotlist).getActionView();
ui_hot = (TextView) menu_hotlist.findViewById(R.id.hotlist_hot);
updateHotCount(hot_number);
new MyMenuItemStuffListener(menu_hotlist, "Show hot message") {
#Override
public void onClick(View v) {
onHotlistSelected();
}
};
return super.onCreateOptionsMenu(menu);
}
// call the updating code on the main thread,
// so we can call this asynchronously
public void updateHotCount(final int new_hot_number) {
hot_number = new_hot_number;
if (ui_hot == null) return;
runOnUiThread(new Runnable() {
#Override
public void run() {
if (new_hot_number == 0)
ui_hot.setVisibility(View.INVISIBLE);
else {
ui_hot.setVisibility(View.VISIBLE);
ui_hot.setText(Integer.toString(new_hot_number));
}
}
});
}
static abstract class MyMenuItemStuffListener implements View.OnClickListener, View.OnLongClickListener {
private String hint;
private View view;
MyMenuItemStuffListener(View view, String hint) {
this.view = view;
this.hint = hint;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
#Override abstract public void onClick(View v);
#Override public boolean onLongClick(View v) {
final int[] screenPos = new int[2];
final Rect displayFrame = new Rect();
view.getLocationOnScreen(screenPos);
view.getWindowVisibleDisplayFrame(displayFrame);
final Context context = view.getContext();
final int width = view.getWidth();
final int height = view.getHeight();
final int midy = screenPos[1] + height / 2;
final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
Toast cheatSheet = Toast.makeText(context, hint, Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT,
screenWidth - screenPos[0] - width / 2, height);
} else {
cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
}
cheatSheet.show();
return true;
}
}
Note: Due to lack to reputation, unable to post pic.
I'd like to create a full width navigation drawer. Setting layout_width to match_parent on #+id/left_drawer yields in width of about 80% of screen space. This seems to be the standard behavior. Do I have to override onMeasure() of DrawerLayout?
My current code:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
android:id="#+id/mainFragmentContainer">
</FrameLayout>
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"/>
</android.support.v4.widget.DrawerLayout>
Thanks.
If you want simpler solution you can just set negative margin
android:layout_marginLeft="-64dp"
for your left_drawer:
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"
android:layout_marginLeft="-64dp"/>
Because all these answers did not work on OS 6.0.1, I'll post here the solution that worked for me in combination with DrawerLayout + NavigationView.
So all what I do is change the width of the NavigationView programatically:
mNavigationView = (NavigationView) findViewById(R.id.nv_navigation);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) mNavigationView.getLayoutParams();
params.width = metrics.widthPixels;
mNavigationView.setLayoutParams(params);
This works for all screen sizes.
Yes, you have to extend DrawerLayout and override some methods because MIN_DRAWER_MARGIN is private
Here is a possible solution:
public class FullDrawerLayout extends DrawerLayout {
private static final int MIN_DRAWER_MARGIN = 0; // dp
public FullDrawerLayout(Context context) {
super(context);
}
public FullDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
throw new IllegalArgumentException(
"DrawerLayout must be measured with MeasureSpec.EXACTLY.");
}
setMeasuredDimension(widthSize, heightSize);
// Gravity value for each drawer we've seen. Only one of each permitted.
int foundDrawers = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
// Content views get measured at exactly the layout's size.
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
child.measure(contentWidthSpec, contentHeightSpec);
} else if (isDrawerView(child)) {
final int childGravity =
getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((foundDrawers & childGravity) != 0) {
throw new IllegalStateException("Child drawer has absolute gravity " +
gravityToString(childGravity) + " but this already has a " +
"drawer view along that edge");
}
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
lp.width);
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(drawerWidthSpec, drawerHeightSpec);
} else {
throw new IllegalStateException("Child " + child + " at index " + i +
" does not have a valid layout_gravity - must be Gravity.LEFT, " +
"Gravity.RIGHT or Gravity.NO_GRAVITY");
}
}
}
boolean isContentView(View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = Gravity.getAbsoluteGravity(gravity,
child.getLayoutDirection());
return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
}
int getDrawerViewGravity(View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
}
static String gravityToString(int gravity) {
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
return "LEFT";
}
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
return "RIGHT";
}
return Integer.toHexString(gravity);
}
}
Based on the Robert's Answer, you can use the layout_marginLeft=-64dp to solve this problem easily.
However it doesn't seems to work anymore on Android 5.0 and above. So here's my solution that worked for me.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
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:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="-64dp"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="#layout/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="64dp"/>
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"/>
</android.support.v4.widget.DrawerLayout>
Basically, Add android:layout_marginRight="-64dp" to the root DrawerLayout so all the layout will go to the right for 64dp.
Then I add the layout_marginRight=64dp to the content so it goes back to the original position. Then you can have a full drawer there.
A variant on Grogory's solution:
Instead of subclassing I call the following utility method right after I grab a reference to the drawer layout:
/**
* The specs tell that
* <ol>
* <li>Navigation Drawer should be at most 5*56dp wide on phones and 5*64dp wide on tablets.</li>
* <li>Navigation Drawer should have right margin of 56dp on phones and 64dp on tablets.</li>
* </ol>
* yet the minimum margin is hardcoded to be 64dp instead of 56dp. This fixes it.
*/
public static void fixMinDrawerMargin(DrawerLayout drawerLayout) {
try {
Field f = DrawerLayout.class.getDeclaredField("mMinDrawerMargin");
f.setAccessible(true);
f.set(drawerLayout, 0);
drawerLayout.requestLayout();
} catch (Exception e) {
e.printStackTrace();
}
}
Nipper's FullDrawerLayout Class is just simply awesome.. it's performance is also faster than the default drawer how ever you can;t use it on devices with api that don't have view.getLayoutDirection();
(i'e : Class doesn;t work on all gingerbread devices )
so what i did was
replaced all
view.getLayoutDirection();
with the below code
GravityCompat.getAbsoluteGravity(gravity,ViewCompat.getLayoutDirection(this));
I have my support library updated to the latest also have extended the fullDrawerlayout to the support navigational drawer. Now it works fine Gingerbread devices as well
Another possible way to solve the issue without overriding too much:
public class FullScreenDrawerLayout extends DrawerLayout {
... //List of constructors calling
... //super(...);
... //init();
/** Make DrawerLayout to take the whole screen. */
protected void init() {
try {
Field field = getClass().getSuperclass().getDeclaredField("mMinDrawerMargin");
field.setAccessible(true);
field.set(this, Integer.valueOf(0));
} catch (Exception e) {
throw new IllegalStateException("android.support.v4.widget.DrawerLayout has changed and you have to fix this class.", e);
}
}
}
If, at some point, support library is updated and mMinDrawerMargin is not there anymore you will get exception and fix problem before you publish your next update.
I didn't make measurements, but suppose there is not so many reflection to affect performance. Furthermore, it executes only per view creation.
PS
it's strange why DrawerLayout is made so inflexible (I'm about private min margin) at this point...
Try out this worked for me :
<include
android:id="#+id/left_drawer"
android:orientation="vertical"
android:layout_width="320dp"
android:layout_height="match_parent"
android:layout_gravity="start"
layout="#layout/drawer"/>
Set width of included layout android:layout_width="320dp". For devices with different screen size you can dynamically set the width of this included layout.
You can use this. Inspired by this post, I've upgraded for the 5th edition. Because it was having problems with StatusBar in versions 5 and later.
you have to extend DrawerLayout and override some methods because MIN_DRAWER_MARGIN is private
public class FullDrawerLayout extends DrawerLayout {
private static final int MIN_DRAWER_MARGIN = 0; // dp
public FullDrawerLayout(Context context) {
super(context);
}
public FullDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
throw new IllegalArgumentException(
"DrawerLayout must be measured with MeasureSpec.EXACTLY.");
}
setMeasuredDimension(widthSize, heightSize);
//for support Android 5+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
params.topMargin = getStatusBarHeight();
setLayoutParams(params);
}
// Gravity value for each drawer we've seen. Only one of each permitted.
int foundDrawers = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
// Content views get measured at exactly the layout's size.
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
child.measure(contentWidthSpec, contentHeightSpec);
} else if (isDrawerView(child)) {
final int childGravity =
getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((foundDrawers & childGravity) != 0) {
throw new IllegalStateException("Child drawer has absolute gravity " +
gravityToString(childGravity) + " but this already has a " +
"drawer view along that edge");
}
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
lp.width);
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
lp.topMargin + lp.bottomMargin,
lp.height);
child.measure(drawerWidthSpec, drawerHeightSpec);
} else {
throw new IllegalStateException("Child " + child + " at index " + i +
" does not have a valid layout_gravity - must be Gravity.LEFT, " +
"Gravity.RIGHT or Gravity.NO_GRAVITY");
}
}
}
boolean isContentView(View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = Gravity.getAbsoluteGravity(gravity,
child.getLayoutDirection());
return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
}
int getDrawerViewGravity(View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
}
static String gravityToString(int gravity) {
if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
return "LEFT";
}
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
return "RIGHT";
}
return Integer.toHexString(gravity);
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}
you can by below code
int width = getResources().getDisplayMetrics().widthPixels/2;
DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) drawer_Linear_layout.getLayoutParams();
params.width = width;
drawer_Linear_layout.setLayoutParams(params);
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/app_bar_dashboard"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_marginRight="32dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<include layout="#layout/view_navigation_menu" />
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
That's works perfectly for me. Hope help others.
Google recommends having a maxim width of 320 dip as per the UI guidelines here.
Moreover, the width can be set by specified the layout_width of the left_drawer ListView.
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".UserListActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:background="#drawable/common_gradient"
android:layoutDirection="rtl"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.2">
<TextView
android:id="#+id/userType_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="نوع المستخدم"
android:textColor="#000000"
android:textSize="20sp"
tools:text="نوع المستخدم" />
<TextView
android:id="#+id/className_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/userType_textView"
android:layout_centerHorizontal="true"
android:text="إسم القسم"
android:textColor="#000000"
android:textSize="16sp"
tools:text="إسم القسم" />
<ImageButton
android:layout_width="30dp"
android:layout_height="20dp"
android:layout_alignBottom="#+id/userType_textView"
android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
android:background="#android:color/transparent"
android:contentDescription="#string/desc"
android:onClick="showMenuAction"
android:scaleType="fitCenter"
android:src="#drawable/menu" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.8"
android:background="#FAFAFA">
<SearchView
android:id="#+id/user_searchView"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#9CC3D7" />
<ListView
android:id="#+id/users_listView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_below="#+id/user_searchView"
android:layout_centerHorizontal="true"
android:divider="#DFDEE1"
android:dividerHeight="1dp" />
</RelativeLayout>
</LinearLayout>
<android.support.v4.widget.DrawerLayout
android:id="#+id/navigationDrawerUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutDirection="rtl">
<ExpandableListView
android:id="#+id/menu_listView_user"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#195269"
android:choiceMode="singleChoice"
android:divider="#2C637D"
android:dividerHeight="1dp"
android:groupIndicator="#null">
</ExpandableListView>
</android.support.v4.widget.DrawerLayout>
Everyone thinks that full-width Sidebar Drawer layout creation is very complicated, but it's very simple if you are following this layout pattern, you don't need to set any minus value.
This is my MainActivity.xml:
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#color/white"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Main Activity -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/toolbarMain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="#layout/layout_profile_toolbar"/>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/app_navigation" />
</LinearLayout>
<!-- Main Activity End -->
<!-- Custom Navigation Drawer Start -->
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true">
<include
android:id="#+id/custom_nav"
android:layout_width="match_parent"
android:layout_height="match_parent"
layout="#layout/fragment_profile"/>
</com.google.android.material.navigation.NavigationView>
<!-- Custom Navigation Drawer End -->
</androidx.drawerlayout.widget.DrawerLayout>
You can set width programmatically.
Give screen full width to navigation view's width.
NavigationView navigationView = findViewById(R.id.nav_view);
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) navigationView.getLayoutParams();
params.width = Utils.screenWidth(this);
You can also take a look at SlidingDrawer class. It's a deprecated class, but as the documentation says you can write your own implementation based on its source code.
Can a SeekBar be vertical? I am not very good at UI design, so how can I make the SeekBar more beautiful, please give me some templates and examples.
For API 11 and later, can use seekbar's XML attributes(android:rotation="270") for vertical effect.
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="270"/>
For older API level (ex API10), only use Selva's answer:
https://github.com/AndroSelva/Vertical-SeekBar-Android
Here is a very good implementation of vertical seekbar.
Have a look.
http://560b.sakura.ne.jp/android/VerticalSlidebarExample.zip
And Here is my own implementation for Vertical and Inverted Seekbar based on this
https://github.com/AndroSelva/Vertical-SeekBar-Android
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(),0);
super.onDraw(c);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
int i=0;
i=getMax() - (int) (getMax() * event.getY() / getHeight());
setProgress(i);
Log.i("Progress",getProgress()+"");
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
Working example
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class VerticalSeekBar extends SeekBar {
public VerticalSeekBar(Context context) {
super(context);
}
public VerticalSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public VerticalSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(h, w, oldh, oldw);
}
#Override
public synchronized void setProgress(int progress) // it is necessary for calling setProgress on click of a button
{
super.setProgress(progress);
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
protected void onDraw(Canvas c) {
c.rotate(-90);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
setProgress(getMax() - (int) (getMax() * event.getY() / getHeight()));
onSizeChanged(getWidth(), getHeight(), 0, 0);
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
}
There, paste the code and save it. Now use it in your XML layout:
<android.widget.VerticalSeekBar
android:id="#+id/seekBar1"
android:layout_width="wrap_content"
android:layout_height="200dp"
/>
Make sure to create a package android.widget and create VerticalSeekBar.java under this package
Try:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:rotation="270"
/>
</RelativeLayout>
We made a vertical SeekBar by using android:rotation="270":
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="#+id/camera_sv_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="#+id/camera_lv_expose"
android:layout_width="32dp"
android:layout_height="200dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_marginRight="15dp"
android:orientation="vertical">
<TextView
android:id="#+id/camera_tv_expose"
android:layout_width="32dp"
android:layout_height="20dp"
android:textColor="#FFFFFF"
android:textSize="15sp"
android:gravity="center"/>
<FrameLayout
android:layout_width="32dp"
android:layout_height="180dp"
android:orientation="vertical">
<SeekBar
android:id="#+id/camera_sb_expose"
android:layout_width="180dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:rotation="270"/>
</FrameLayout>
</LinearLayout>
<TextView
android:id="#+id/camera_tv_help"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:text="#string/camera_tv"
android:textColor="#FFFFFF" />
</RelativeLayout>
Screenshot for camera exposure compensation:
I used Selva's solution but had two kinds of issues:
OnSeekbarChangeListener did not work properly
Setting progress programmatically did not work properly.
I fixed these two issues. You can find the solution (within my own project package) at
https://github.com/jeisfeld/Augendiagnose/blob/master/AugendiagnoseIdea/augendiagnoseLib/src/main/java/de/jeisfeld/augendiagnoselib/components/VerticalSeekBar.java
This worked for me, just put it into any layout you want to.
<FrameLayout
android:layout_width="32dp"
android:layout_height="192dp">
<SeekBar
android:layout_width="192dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:rotation="270" />
</FrameLayout>
Wrap it inside a FrameLayout so that there is no Size issue.
<FrameLayout
android:layout_width="#dimen/_20dp"
android:layout_marginStart="#dimen/_15dp"
android:layout_marginEnd="#dimen/_15dp"
android:layout_height="match_parent"
android:orientation="vertical">
<SeekBar
android:layout_width="150dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:rotation="270" />
</FrameLayout>
Note, it appears to me that if you change the width the thumb width does not change correctly.
I didn't take the time to fix it right, i just fixed it for my case. Here is what i did.
Couldn't figure out how to contact the original creator.
public void setThumb(Drawable thumb) {
if (thumb != null) {
thumb.setCallback(this);
// Assuming the thumb drawable is symmetric, set the thumb offset
// such that the thumb will hang halfway off either edge of the
// progress bar.
//This was orginally divided by 2, seems you have to adjust here when you adjust width.
mThumbOffset = (int)thumb.getIntrinsicHeight();
}
When moving the thumb with an EditText, the Vertical Seekbar setProgress may not work. The following code can help:
#Override
public synchronized void setProgress(int progress) {
super.setProgress(progress);
updateThumb();
}
private void updateThumb() {
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
This snippet code found here:
https://stackoverflow.com/a/33064140/2447726
Try this
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.SeekBar;
/**
* Implementation of an easy vertical SeekBar, based on the normal SeekBar.
*/
public class VerticalSeekBar extends SeekBar {
/**
* The angle by which the SeekBar view should be rotated.
*/
private static final int ROTATION_ANGLE = -90;
/**
* A change listener registrating start and stop of tracking. Need an own listener because the listener in SeekBar
* is private.
*/
private OnSeekBarChangeListener mOnSeekBarChangeListener;
/**
* Standard constructor to be implemented for all views.
*
* #param context The Context the view is running in, through which it can access the current theme, resources, etc.
* #see android.view.View#View(Context)
*/
public VerticalSeekBar(final Context context) {
super(context);
}
/**
* Standard constructor to be implemented for all views.
*
* #param context The Context the view is running in, through which it can access the current theme, resources, etc.
* #param attrs The attributes of the XML tag that is inflating the view.
* #see android.view.View#View(Context, AttributeSet)
*/
public VerticalSeekBar(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
/**
* Standard constructor to be implemented for all views.
*
* #param context The Context the view is running in, through which it can access the current theme, resources, etc.
* #param attrs The attributes of the XML tag that is inflating the view.
* #param defStyle An attribute in the current theme that contains a reference to a style resource that supplies default
* values for the view. Can be 0 to not look for defaults.
* #see android.view.View#View(Context, AttributeSet, int)
*/
public VerticalSeekBar(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
protected final void onSizeChanged(final int width, final int height, final int oldWidth, final int oldHeight) {
super.onSizeChanged(height, width, oldHeight, oldWidth);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
protected final synchronized void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(heightMeasureSpec, widthMeasureSpec);
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
protected final void onDraw(#NonNull final Canvas c) {
c.rotate(ROTATION_ANGLE);
c.translate(-getHeight(), 0);
super.onDraw(c);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
public final void setOnSeekBarChangeListener(final OnSeekBarChangeListener listener) {
// Do not use super for the listener, as this would not set the fromUser flag properly
mOnSeekBarChangeListener = listener;
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
public final boolean onTouchEvent(#NonNull final MotionEvent event) {
if (!isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStartTrackingTouch(this);
}
break;
case MotionEvent.ACTION_MOVE:
setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
break;
case MotionEvent.ACTION_UP:
setProgressInternally(getMax() - (int) (getMax() * event.getY() / getHeight()), true);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStopTrackingTouch(this);
}
break;
case MotionEvent.ACTION_CANCEL:
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onStopTrackingTouch(this);
}
break;
default:
break;
}
return true;
}
/**
* Set the progress by the user. (Unfortunately, Seekbar.setProgressInternally(int, boolean) is not accessible.)
*
* #param progress the progress.
* #param fromUser flag indicating if the change was done by the user.
*/
public final void setProgressInternally(final int progress, final boolean fromUser) {
if (progress != getProgress()) {
super.setProgress(progress);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
}
}
onSizeChanged(getWidth(), getHeight(), 0, 0);
}
/*
* (non-Javadoc) ${see_to_overridden}
*/
#Override
public final void setProgress(final int progress) {
setProgressInternally(progress, false);
}
}
Getting started
Add these lines to build.gradle.
dependencies {
compile 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:0.7.2'
}
Usage
Java code
public class TestVerticalSeekbar extends AppCompatActivity {
private SeekBar volumeControl = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_vertical_seekbar);
volumeControl = (SeekBar) findViewById(R.id.mySeekBar);
volumeControl.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
int progressChanged = 0;
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progressChanged = progress;
}
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
public void onStopTrackingTouch(SeekBar seekBar) {
Toast.makeText(getApplicationContext(), "seek bar progress:" + progressChanged,
Toast.LENGTH_SHORT).show();
}
});
}
}
Layout XML
<!-- This library requires pair of the VerticalSeekBar and VerticalSeekBarWrapper classes -->
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
android:layout_width="wrap_content"
android:layout_height="150dp">
<com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
android:id="#+id/mySeekBar"
android:layout_width="0dp"
android:layout_height="0dp"
android:max="100"
android:progress="0"
android:splitTrack="false"
app:seekBarRotation="CW90" /> <!-- Rotation: CW90 or CW270 -->
</com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>
NOTE: android:splitTrack="false" is required for Android N+.
I tried in many different ways, but the one which worked for me was.
Use Seekbar inside FrameLayout
<FrameLayout
android:id="#+id/VolumeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/MuteButton"
android:layout_below="#id/volumeText"
android:layout_centerInParent="true">
<SeekBar
android:id="#+id/volume"
android:layout_width="500dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:progress="50"
android:secondaryProgress="40"
android:progressDrawable="#drawable/seekbar_volume"
android:secondaryProgressTint="#color/tint_neutral"
android:thumbTint="#color/tint_neutral"
/>
And in Code.
Setup Pre Draw callback on Seekbar, Where you can change the Width and height of the Seekbar
I did this part in c#, so Code i used was
var volumeSlider = view.FindViewById<SeekBar>(Resource.Id.home_link_volume);
var volumeFrameLayout = view.FindViewById<FrameLayout>(Resource.Id.linkVolumeFrameLayout);
void OnPreDrawVolume(object sender, ViewTreeObserver.PreDrawEventArgs e)
{
volumeSlider.ViewTreeObserver.PreDraw -= OnPreDrawVolume;
var h = volumeFrameLayout.Height;
volumeSlider.Rotation = 270.0f;
volumeSlider.LayoutParameters.Width = h;
volumeSlider.RequestLayout();
}
volumeSlider.ViewTreeObserver.PreDraw += OnPreDrawVolume;
Here i Add listener to PreDraw Event and when its triggered, I remove the PreDraw so that it doesnt go into Infinite loop.
So when Pre Draw gets executed, I fetch the Height of FrameLayout and assign it to Seekbar. And set the rotation of seekbar to 270.
As my seekbar is inside frame Layout and its Gravity is set as Center. I dont need to worry about the Translation. As Seekbar always stay in middle of Frame Layout.
Reason i remove EventHandler is because seekbar.RequestLayout(); Will make this event to be executed again.
You can do it by yourself - it's now so difficult.
Here is an example from my project: https://github.com/AlShevelev/WizardCamera
Let start from settings (attrs.xml).
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpositionBar">
<attr name="button_icon" format="reference" />
<attr name="button_icon_size" format="dimension" />
<attr name="stroke_width" format="dimension" />
<attr name="stroke_color" format="color" />
<attr name="button_color" format="color" />
<attr name="button_color_pressed" format="color" />
<attr name="min_value" format="float" />
<attr name="max_value" format="float" />
</declare-styleable>
</resources>
Here is a couple of utility functions:
fun <T: Comparable<T>>T.fitInRange(range: Range<T>): T =
when {
this < range.lower -> range.lower
this > range.upper -> range.upper
else -> this
}
fun Float.reduceToRange(rangeFrom: Range<Float>, rangeTo: Range<Float>): Float =
when {
this == rangeFrom.lower -> rangeTo.lower
this == rangeFrom.upper -> rangeTo.upper
else -> {
val placeInRange = (this - rangeFrom.lower) / (rangeFrom.upper - rangeFrom.lower)
((rangeTo.upper - rangeTo.lower) * placeInRange) + rangeTo.lower
}
}
And at last, but not least - a class for vertical seek bar:
class ExpositionBar
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val drawingRect = RectF(0f, 0f, 0f, 0f)
private val drawingPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val strokeWidth: Float
#ColorInt
private val strokeColor: Int
#ColorInt
private val buttonFillColor: Int
#ColorInt
private val buttonFillColorPressed: Int
private val icon: VectorDrawable
private val valuesRange: Range<Float>
private var centerX = 0f
private var minY = 0f
private var maxY = 0f
private var buttonCenterY = 0f
private var buttonRadiusExt = 0f
private var buttonRadiusInt = 0f
private var buttonMinY = 0f
private var buttonMaxY = 0f
private var buttonCenterBoundsRange = Range(0f, 0f)
private var iconTranslationX = 0f
private var iconTranslationY = 0f
private var isInDragMode = false
private var onValueChangeListener: ((Float) -> Unit)? = null
private var oldOutputValue = Float.MIN_VALUE
init {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ExpositionBar)
icon = typedArray.getDrawable(R.styleable.ExpositionBar_button_icon) as VectorDrawable
val iconSize = typedArray.getDimensionPixelSize(R.styleable.ExpositionBar_button_icon_size, 0)
icon.setBounds(0, 0, iconSize, iconSize)
strokeWidth = typedArray.getDimensionPixelSize(R.styleable.ExpositionBar_stroke_width, 0).toFloat()
drawingPaint.strokeWidth = strokeWidth
strokeColor = typedArray.getColor(R.styleable.ExpositionBar_stroke_color, Color.WHITE)
buttonFillColor = typedArray.getColor(R.styleable.ExpositionBar_button_color, Color.BLACK)
buttonFillColorPressed = typedArray.getColor(R.styleable.ExpositionBar_button_color_pressed, Color.BLUE)
val minValue = typedArray.getFloat(R.styleable.ExpositionBar_min_value, 0f)
val maxValue = typedArray.getFloat(R.styleable.ExpositionBar_max_value, 0f)
valuesRange = Range(minValue, maxValue)
typedArray.recycle()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
drawingRect.right = width.toFloat()
drawingRect.bottom = height.toFloat()
buttonCenterY = drawingRect.centerY()
recalculateDrawingValues()
}
override fun onDraw(canvas: Canvas) {
drawingPaint.color = strokeColor
drawingPaint.style = Paint.Style.STROKE
// Draw the center line
canvas.drawLine(centerX, minY, centerX, buttonMinY, drawingPaint)
canvas.drawLine(centerX, buttonMaxY, centerX, maxY, drawingPaint)
// Draw the button
canvas.drawCircle(centerX, buttonCenterY, buttonRadiusExt, drawingPaint)
drawingPaint.style = Paint.Style.FILL
drawingPaint.color = if(isInDragMode) buttonFillColorPressed else buttonFillColor
canvas.drawCircle(centerX, buttonCenterY, buttonRadiusInt, drawingPaint)
// Draw button icon
canvas.translate(iconTranslationX, iconTranslationY)
icon.draw(canvas)
canvas.translate(-iconTranslationX, -iconTranslationY)
}
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if(!isEnabled) {
return false
}
when(event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if(isButtonHit(event.y)){
isInDragMode = true
invalidate()
}
}
MotionEvent.ACTION_MOVE -> {
if(isInDragMode) {
buttonCenterY = event.y.fitInRange(buttonCenterBoundsRange)
recalculateDrawingValues()
invalidate()
val outputValue = buttonCenterY.reduceToRange(buttonCenterBoundsRange, valuesRange)
if (outputValue != oldOutputValue) {
onValueChangeListener?.invoke(outputValue)
oldOutputValue = outputValue
}
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
isInDragMode = false
invalidate()
}
}
return true
}
fun setOnValueChangeListener(listener: ((Float) -> Unit)?) {
onValueChangeListener = listener
}
private fun recalculateDrawingValues() {
centerX = drawingRect.left + drawingRect.width()/2
minY = drawingRect.top
maxY = drawingRect.bottom
buttonRadiusExt = drawingRect.width() / 2 - strokeWidth / 2
buttonRadiusInt = buttonRadiusExt - strokeWidth / 2
buttonMinY = buttonCenterY - buttonRadiusExt
buttonMaxY = buttonCenterY + buttonRadiusExt
val buttonCenterMinY = minY + buttonRadiusExt + strokeWidth / 2
val buttonCenterMaxY = maxY - buttonRadiusExt - strokeWidth / 2
buttonCenterBoundsRange = Range(buttonCenterMinY, buttonCenterMaxY)
iconTranslationX = centerX - icon.bounds.width() / 2
iconTranslationY = buttonCenterY - icon.bounds.height() / 2
}
private fun isButtonHit(y: Float): Boolean {
return y >= buttonMinY && y <= buttonMaxY
}
}
You can use it as shown here:
<com.shevelev.wizard_camera.main_activity.view.widgets.ExpositionBar
android:id="#+id/expositionBar"
android:layout_width="#dimen/mainButtonSize"
android:layout_height="300dp"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="#dimen/marginNormal"
android:layout_marginBottom="26dp"
app:button_icon = "#drawable/ic_brightness"
app:button_icon_size = "#dimen/toolButtonIconSize"
app:stroke_width = "#dimen/strokeWidthNormal"
app:stroke_color = "#color/mainButtonsForeground"
app:button_color = "#color/mainButtonsBackground"
app:button_color_pressed = "#color/mainButtonsBackgroundPressed"
app:min_value="-100"
app:max_value="100"
/>
Voila!
Simple answer
Instead of using android:rotation="270" inside of a seek bar, use it inside of a FrameLayout that wraps around it.
<FrameLayout
android:background="#color/gray"
android:layout_width="300dp"
android:layout_height="5dp"
android:layout_marginEnd="-126dp"
android:rotation="270"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<SeekBar
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
To get my frame layout to be 24dp margin right I calculated width -150dp + 24dp because the frame layout is first drawn horizontally and then rotated vertically.
In my case, I used an ordinary seekBar and just flipped out the layout.
seekbark_layout.xml - my layout that containts seekbar which we need to make vertical.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SeekBar
android:id="#+id/seekBar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.vgfit.seekbarexample.MainActivity">
<View
android:id="#+id/headerView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#color/colorAccent"/>
<View
android:id="#+id/bottomView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:background="#color/colorAccent"/>
<include
layout="#layout/seekbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/bottomView"
android:layout_below="#id/headerView"/>
</RelativeLayout>
And in MainActivity I rotate seekbar_layout:
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.RelativeLayout
import kotlinx.android.synthetic.main.seekbar_layout.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rootView.post {
val w = rootView.width
val h = rootView.height
rootView.rotation = 270.0f
rootView.translationX = ((w - h) / 2).toFloat()
rootView.translationY = ((h - w) / 2).toFloat()
val lp = rootView.layoutParams as RelativeLayout.LayoutParams
lp.height = w
lp.width = h
rootView.requestLayout()
}
}
}
As a result we have necessary vertical seekbar:
By using RotateLayout, having vertical SeekBar is a breeze. Just wrap that horrible SeekBar into it and Bob is your uncle:
<com.github.rongi.rotate_layout.layout.RotateLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:angle="-90"
>
<androidx.appcompat.widget.AppCompatSeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</com.github.rongi.rotate_layout.layout.RotateLayout>
https://github.com/rongi/rotate-layout
Hidden Vertical SeekBar
In a simpler way, you can make a SeekBar like this.
Like the way to increase or decrease the volume in video players. All
three Last attributes can be manipulated in the SeekBar.
<LinearLayout
android:layout_width="#dimen/_40dp"
android:layout_height="wrap_content"
android:layout_marginVertical="100dp"
android:gravity="center"
android:orientation="vertical"
>
<SeekBar
android:layout_width="500dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:rotation="270"
android:secondaryProgress="6"
android:progress="15"
android:progressDrawable="#null"
android:thumbTint="#null"
android:secondaryProgressTint="#null"
/>
</LinearLayout>