I'm currently developing a simple Notes application where the user can input a title and the content of their note. What I am looking to achieve is that when the user clicks the note content (EditText) the soft keyboard comes up and only the note content EditText reduces in size (resizes) whilst everything else remains in the same position.
My current implementation can be seen below:
Manifest:
<activity android:theme="#style/AppTheme"
android:name=".AddActivity"
android:label="#string/add_record"
android:windowSoftInputMode="adjustResize"
android:parentActivityName=".MainActivity"
android:excludeFromRecents="true"/>
XML - Add Activity
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/add_record"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<EditText
android:id="#+id/title_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/enter_title"
android:inputType="textCapSentences"
android:textColor="#color/fontPrimary"
android:theme="#style/EditTextCustomCursor">
<requestFocus />
</EditText>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/modify_scrollview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fitsSystemWindows="false"
android:isScrollContainer="false"
android:fillViewport="true">
<EditText
android:id="#+id/note_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#null"
android:ellipsize="end"
android:gravity="top|left"
android:hint="#string/enter_note"
android:inputType="textCapSentences|textMultiLine"
android:paddingLeft="5dp"
android:scrollHorizontally="false"
android:textColor="#color/fontPrimary"
android:theme="#style/EditTextCustomCursor" />
</ScrollView>
</LinearLayout>
Java - Add Activity
private int screenHeight;
private int actionBarHeight = 350;
private int keyboardHeight;
...
private void setupListeners() {
final LinearLayout layout = (LinearLayout) findViewById(R.id.add_record);
layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Rect r = new Rect();
layout.getWindowVisibleDisplayFrame(r);
screenHeight = layout.getRootView().getHeight();
keyboardHeight = screenHeight - (r.bottom - r.top);
Log.d("Keyboard Size", "Size: " + keyboardHeight);
}
});
KeyboardVisibilityEvent.setEventListener(
AddActivity.this,
new KeyboardVisibilityEventListener() {
#Override
public void onVisibilityChanged(boolean isOpen) {
if (isOpen) {
Log.d("KB", "openKeyboard");
scrollView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT, screenHeight - actionBarHeight - keyboardHeight));
} else {
Log.d("KB", "closeKeyboard");
scrollView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT));
}
}
});
}
This is using the Keyboard library (https://github.com/yshrsmz/KeyboardVisibilityEvent) to detect when the keyboard is opened or closed. This works perfectly and the height / layout is adjusted just how I want it to look but only if the user clicks at the top of the EditText. If the user clicks at the bottom of the EditText (if they have entered a long note) then the whole layout gets pushed up leaving a large gap at the bottom of the page.
Therefore, is there any way how wherever in the EditText / ScrollView the user clicks, for it to only adjust that one EditText in height and leave the other EditText in place at the top of the screen without pushing it and the SupportActionBar out of view? Also, the ScrollView is being used to achieve the vertical scrollbar on the right side of the screen - if this same behaviour can be achieved using just the EditText, then I would remove the ScrollView altogether.
EDIT - Add Photos
Image 1: Long note content (bottom of note content is at the bottom of the scrollView (which cannot be seen, until scrolled))
Image 2: Same note but clicking at the bottom, forces the top EditText and Support ActionBar out of view whilst leaving a gap at the bottom.
Explanation: Where the F is highlighted (in Image 2) that is the bottom of the EditText / ScrollView so you can see the large gap created between the top of the soft keyboard and the bottom of the EditText / ScrollView
Desired behaviour: Clicking anywhere in the bottom EditText should only resize that particular EditText to make room for the soft keyboard and ensure that this EditText is above the soft keyboard so the user can see what they are typing whilst the top EditText remains in the same position throughout.
Its because you're adding edittext in a scroll view. why do you even need scroll view? scroll view have a property of going to specific line when keyboard pop-up which is causing this behavior. if you really want to use scrollview, then add master layout as scrollview. add one direct child aka linear layout in there and add all the content in that linear layout.
I have managed to resolve most of this by doing the following:
Removing the ScrollView
Subclassing EditText (to receive the close keyboard button)
Adding a height-change listener
Adding the scroll bars property to the EditText
Manifest:
<activity android:theme="#style/AppTheme"
android:name=".AddActivity"
android:label="#string/add_record"
android:windowSoftInputMode="adjustNothing"
android:parentActivityName=".MainActivity"
android:excludeFromRecents="true"/>
XML - Add Activity:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/add_record"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp"
android:windowSoftInputMode="stateHidden">
<EditText
android:id="#+id/title_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/enter_title"
android:inputType="textCapSentences"
android:textColor="#color/fontPrimary"
android:theme="#style/EditTextCustomCursor">
<requestFocus />
</EditText>
<com.securenotes.ExtendedEditText
android:id="#+id/note_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#null"
android:ellipsize="end"
android:gravity="top|left"
android:hint="#string/enter_note"
android:inputType="textCapSentences|textMultiLine"
android:lines="50"
android:maxLines="20"
android:minLines="5"
android:paddingLeft="5dp"
android:scrollHorizontally="false"
android:scrollbars="vertical"
android:textColor="#color/fontPrimary"
android:theme="#style/EditTextCustomCursor" />
</LinearLayout>
Java - Add Activity:
private Boolean initialStart = true;
private Boolean isOpened = false;
...
private void setupListeners() {
final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
Log.d("KB", "HeightDiff: " + heightDiff);
if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
if (!isOpened && initialStart) {
Log.d("KB", "1) openKeyboard");
//Do two things, make the view top visible and the editText smaller
noteEditText.setLines(15);
noteEditText.requestLayout();
initialStart = false;
isOpened = true;
} else if (!isOpened && noteEditText.hasFocus()) {
Log.d("KB", "2) openKeyboard");
//Do two things, make the view top visible and the editText smaller
noteEditText.setLines(15);
noteEditText.requestLayout();
isOpened = true;
}
}
}
});
noteEditText.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("KB", "EditText onClick");
isOpened = false;
}
});
noteEditText.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
Log.d("KB", "closeKeyboard");
noteEditText.setLines(50);
noteEditText.requestLayout();
}
return false;
}
});
}
Java - Subclassed EditText:
public class ExtendedEditText extends EditText {
public ExtendedEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ExtendedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExtendedEditText(Context context) {
super(context);
}
#Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
dispatchKeyEvent(event);
return false;
}
return super.onKeyPreIme(keyCode, event);
}
}
However, the one remaining issue is that if the user is scrolling the EditText, sometimes it detects it as a click rather than a scroll so the keyboard is then made visible and the layout (number of lines) is changed. I looked into the onScrollChangeListener but this requires API 23 and my current minimum is 15 - is there any way around this to tell the difference between a scroll and an actual click on the EditText?
Related
Today, I used an Android machine with poor performance. I found the checkbox loading is very slow.
For example:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long t1 = System.currentTimeMillis();
setContentView(R.layout.activity_main);
System.out.println("main cost: " + (System.currentTimeMillis() - t1));
}
}
There is only one Checkbox in the page, this will cost 200ms. If there are 3 or 5 checkbox in activity_main.xml, will cost 1s. What happend?
I compared Switch to Checkbox. Obviously Switch is very very better than Checkbox. If i want to keep to use Checkbox, what should i do?
Why does it happen ?
This is happening because the Checkbox class is very complicated(too much code 😠).
What is the solution ?
You can create a custom layout.
You can use a library.
How to implement ?
1.Custom layout
First create a LinearLayout with horizontal orientation.
Add an ImageView and a TextView in the xml.
Code reference
<LinearLayout
android:id="#+id/checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="7dp">
<ImageView
android:id="#+id/checkImageView"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="#drawable/checked"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is the text for checkbox."
android:layout_gravity="center_vertical"
android:layout_marginStart="15dp"/>
</LinearLayout>
Now the code for it to work.
In your class add this
boolean isChecked = true
Now it will function like this
LinearLayout checkBox = findViewById( R.id.checkbox );
ImageView checkBoxImage = findViewById( R.id.checkImageView );
checkBox.setOnClickListener( v -> {
final Bitmap bmap = ((BitmapDrawable)checkBoxImage.getDrawable()).getBitmap();
Drawable myDrawable = getResources().getDrawable(R.drawable.checked);
final Bitmap myLogo = ((BitmapDrawable) myDrawable).getBitmap();
if (bmap.sameAs(myLogo)){
//it is checked
isChecked = false;
checkBoxImage.setImageResource( R.drawable.unchecked );
}else {
isChecked = true;
checkBoxImage.setImageResource( R.drawable.checked );
}
} );
2.Using a library
Use this library.
Sample usage
setChecked(boolean checked); //by default, it's wthout animation
setChecked(boolean checked, boolean animate); //pass true to animate
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
final CustomCheckBox scb = (CustomCheckBox) findViewById(R.id.scb);
scb.setOnCheckedChangeListener(new CustomCheckBox.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CustomCheckBox checkBox, boolean isChecked) {
Log.d("CustomCheckBox", String.valueOf(isChecked));
}
});
}
Attributes
Attr
Type
Description
duration
integer
Animation Duration
stroke_width
dimension
The border width when unchecked
color_tick
color
Tick color (visible only when checked)
color_checked
color
Fill color when selected
color_unchecked
color
Fill color when unchecked
color_unchecked_stroke
color
Border color when unchecked
References
Github
Edit 1 (27/1/22)
Because of the comment received by Decent Dabbler , I now remember what it forgot. I actually forgot to mention that the file that takes too long to load is the xml file.This can be proved as we do not always use the class right? Just the view in the xml. If you open the xml file of the checkbox, you find it too big.
I have grid view which is using GridLayoutManager and the RecyclerView
grid has 3 rows which are working fine as you see,[![enter image description here][1]][1]
but for some reason, image views scale type won't work on any of the items and those items you see work fine because the resolution already is square mean 512*512 but if you see the last item that has low resolution it have white space in left and right.
that's my code from a setup list
int getori = getResources().getConfiguration().orientation;
if (getori == Configuration.ORIENTATION_PORTRAIT) {
gl = new GridLayoutManager(getBaseContext(), 3);
} else if (getori == Configuration.ORIENTATION_LANDSCAPE){
gl = new GridLayoutManager(getBaseContext(), 6);
}
gg.setHasFixedSize(true);
gg.setFitsSystemWindows(true);
gg.setLayoutManager(gl);
gg.setAdapter(startpostsystem);
that's my grid adapter code
#Override
public void onBindViewHolder(Grid_view holder, final int position) {
holder.im.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String getfiledop = fall[position].getAbsolutePath();
Intent is = new Intent(cm,Open_post_item.class);
is.putExtra("req",12);
cm.startActivity(is);
and this is my grid view items layout code.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="140dp">
<ImageView
android:id="#+id/imageView18"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
app:srcCompat="#drawable/sunbow100" />
I tried to use :
android:scaleType="centerCrop"
android:adjustViewBounds="true"
or fitxy it make it worse
view bounds won't work either.
if i add more images like 800*400 or ... it will have white space
so where is problem where I do wrong.
try using android:src in place of app:srcCompat
<ImageView
android:id="#+id/imageView18"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:src="#drawable/sunbow100" />
I have a custom adapter set for my ListView. I have also set the default item selection ripple animation to appear when an item is selected (android:background="?android:selectableItemBackground").
However, the animation won't appear when I tap the TextViews. Here's a video.
What I've tried, to no avail:
setting the parent LinearLayout's descendantFocusability="blocksDescendants"
adding clickable &
focusable="false" to the TextViews
Here's my list_item.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
android:background="?android:selectableItemBackground"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1" />
</LinearLayout>
(Edit) And in getView() method in Adapter class I have these before return convertView:
holder.title.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((ListView) parent).performItemClick(v, position, 0); // Let the event be handled in onItemClick()
}
});
holder.text.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((ListView) parent).performItemClick(v, position, 0); // Let the event be handled in onItemClick()
}
});
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((ListView) parent).performItemClick(v, position, 0);
}
});
...to allow onItemClick in MainActivity (if I press the text or the linear layout, it opens up the same thing).
I have a solution to your problem: Try this in your getView():
final RippleDrawable rippleDrawable = (RippleDrawable) convertView.getBackground();
final View finalParent = parent;
final int finalPosition = position;
View.OnTouchListener listener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
rippleDrawable.setHotspot(motionEvent.getX(), motionEvent.getY());
rippleDrawable.setState(new int[] {android.R.attr.state_pressed, android.R.attr.state_enabled});
return true;
case MotionEvent.ACTION_UP:
rippleDrawable.setState(new int[0]);
((ListView)finalParent).performItemClick(view, finalPosition, 0);
return true;
}
return false;
}
};
The first line gets the RippleDrawable of the ListView item which holds the touched TextView.
When the TextView gets touched, you get the coordinates of the touch and set them to the RippleDrawable with setHotSpot, to specify the starting point of the ripple effect.
After that you trigger the ripple effect by setting an int array holding the states android.R.attr.state_pressed and android.R.attr.state_enabled to the RippleDrawable using setState().
When the touch ends, you set the state to an empty int array, the stop the ripple.
It should work!
I have a numberpicker dialog to select hours and minutes (15 minutes interval). It works fine on large screens but on small screens and older devices it gets stuck if I try to scroll using touch.
I am extending the android.widget.NumberPicker and reducing the font size which helps on some devices.
public class TimerNumberPicker extends android.widget.NumberPicker {
public TimerNumberPicker(Context context) {
super(context);
}
public TimerNumberPicker(Context context, AttributeSet attrs){
super(context,attrs);
}
#Override
public void addView(View child) {
super.addView(child);
updateView(child);
}
#Override
public void addView(View child, ViewGroup.LayoutParams params) {
super.addView(child, params);
updateView(child);
}
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
updateView(child);
}
private void updateView(View view) {
if(view instanceof EditText){
((EditText) view).setTextSize(TypedValue.COMPLEX_UNIT_SP,16);
view.setTag(FontUtil.TAG_CONDENSED_LIGHT);
FontUtil.getInstence(getContext()).setFontByTag(view);
}
}
}
What makes it more complicated is that the minutes have only values (0, 15, 30, 45) and I have to setWrapSelectorWheel to false?
I am thinking if it will be possible to always show the up and down arrows and then disabling scroll.
Change it to an Edittext with the type of number, and use a Toast if the client put a number upper or below of your range. It's more elegant visually, and that will not give you any problem at all
you can use Fantastic PickView library
See PickView
This is a helper lib for us to pick date or province like IOS system WheelView widget.
Maybe you could solve it using a TimePicker.
This could be the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:padding="8dp"
android:layout_height="wrap_content">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TimePicker
android:id="#+id/time_picker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="#+id/date_time_set"
android:layout_width="match_parent"
android:text="Set"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
While the dialog could be included in a normal alert:
private void showTimePicker(){
final View dialogView = View.inflate(getActivity(), <time_picker_container_layout_id>, null);
final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).create();
dialogView.findViewById(R.id.date_time_set).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
TimePicker timePicker = (TimePicker) dialogView.findViewById(R.id.time_picker);
String time = timePicker.getCurrentHour()+":"+timePicker.getCurrentMinute());
dateTime.setText(time);
alertDialog.dismiss();
}});
TimePicker timePicker = (TimePicker) dialogView.findViewById(R.id.time_picker);
timePicker.setCurrentHour(<default_hour>);
timePicker.setCurrentMinute(<default_minute>);
alertDialog.setView(dialogView);
alertDialog.show();
}
If you need to show just the 15', you can round the picked minutes to the closest valid value.
The scrollView in the layout will reduce problems related the picker size.
I hope it helped.
I'm trying to make scrollable RelativeLayout, that contains some custom Views. This is the plan of cinema hall, i have x, y coordinates of places and it's width and height (that are just rectangales, actually). I just put it into this RelativeLayout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:padding="10px">
<ScrollView
android:layout_height="wrap_content"
android:layout_width="fill_parent">
<RelativeLayout android:id="#+id/scrollable_places"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</RelativeLayout>
</ScrollView>
I put it like this:
RelativeLayout layout = (RelativeLayout) context.findViewById(R.id.scrollable_places);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(place.getWidth(),
place.getHeight());
params.leftMargin = place.getX();
params.topMargin = place.getY();
layout.addView(new Seat(context, place), params);
Seat class looks like this:
public class Seat extends View {
private Place place;
private boolean isRed = false;
public Seat(Context context, Place place) {
super(context);
this.place = place;
setOnClickListener(new OnSeatClickListener());
}
#Override
protected void onDraw(Canvas canvas) {
if (!isRed)
canvas.drawColor(Color.GREEN);
else
canvas.drawColor(Color.RED);
}
protected void setRed() {
isRed = true;
}
private class OnSeatClickListener implements OnClickListener {
#Override
public void onClick(View view) {
((Seat) view).setRed();
view.invalidate();
}
}
}
Views are drawing perfectly. But I have ann array of views, and when some of them go out of the screen, scrollView didn't work, there is no scroll on the screen. Have you any ideas how can I make this layout scroll?
You should try following xml file. It will work on all the devices.
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:fillViewport="true"
android:padding="10px"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<RelativeLayout android:id="#+id/scrollable_places"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
</RelativeLayout>
</ScrollView>
Thanks.
In scroll view you have written android:layout_height="wrap_content" instead of using wrap_content five specific height size eg android:layout_height="100dip"
Thanks
Deepak