Textview not displaying in custom viewgroup even when onMeasure() is called - java

So I am just playing around in android and found something really strange. Before I states what is wrong let me give the code.
Here is my Custom Viewgroup code:
package com.ayto.android.cleverpad;
import android.content.Context;
import android.graphics.Color;
import android.graphics.RectF;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
public class noteLayout extends ViewGroup {
float leftOrientationSize = 0;
float rightOrientationSize=0;
public noteLayout(Context activityContext)
{
super(activityContext);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
RelativeLayout mainParent = (RelativeLayout) getParent();
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
child.measure(MeasureSpec.makeMeasureSpec((mainParent.getWidth()/2)-30,MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(200,MeasureSpec.AT_MOST));
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numberOfChild = getChildCount();
for (int i = 0;i<numberOfChild;i++){
View childView = getChildAt(i);
float childHeight = (float) childView.getMeasuredHeight();
float childWidth = (float) childView.getMeasuredWidth();
RectF rect = new RectF();
rect.bottom = childHeight+20;
rect.top = 20;
rect.left = 20;
rect.right = childWidth+20;
childView.layout((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom);
}
}
}
and here is the xml file that I add (addView()) to CustomView:
<?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" android:background="#android:color/holo_blue_bright">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Title"
android:id="#+id/displayNoteTitle"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Test"
android:id="#+id/displayNote"
android:padding="5dp"
android:paddingBottom="10dp"
android:paddingLeft="5dp"
android:ellipsize="end"
android:layout_above="#+id/displayNoteTitle"
android:layout_alignRight="#+id/displayNoteTitle"
android:layout_alignEnd="#+id/displayNoteTitle" />
</RelativeLayout>
So basically what I did was create an instance of my noteLayout note = new noteLayout(getApplicationContext()) and use the layout inflater to inflate the views from my xml. Then I use noteLayout.addView() to add the inflated view into my custom viewgroup. My problem is that only the textview with the android:Text = "Title" is displayed and the other seem to not render. I am not so sure why this is the case.
ActivityMain
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
noteLayout test = new noteLayout(getApplicationContext());
test.addView(getLayoutInflater().inflate(R.layout.Layout,null));
((RelativeLayout) findViewById(R.id.mainActivity)).addView(test);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

Okay. So after our discussion, managed to see what was happening.. I'll explain it thoroughly as it might be confusing.. First of.. In the MainActivity the noteLayout was initialized like so:
noteLayout test = new noteLayout(getApplicationContext());
and is then passed with another RelativeLayout that contains the 2 TextViews :
((RelativeLayout) findViewById(R.id.mainActivity)).addView(test);
So now, the hierarchy would look like this:
RelativeLayout(activity_main)
-- RelativeLayout(one from xml)
---- TextView(title)
---- TextView(body)
And in the code of noteLayout in onMeasured() this line appears:
RelativeLayout mainParent = (RelativeLayout) getParent();
-- which means that the mainParent it gets is the RelativeLayout of the MainActivity (R.layout.activity_main), after that, the loop was made:
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
child.measure(MeasureSpec.makeMeasureSpec((mainParent.getWidth()/2)-30,MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(200,MeasureSpec.AT_MOST));
}
To verify this, I put a log inside the loop and it returns only 1 child. So what I did is modify the loop like so:
RelativeLayout mainParent = (RelativeLayout) getParent();
final RelativeLayout child = (RelativeLayout) getChildAt(0);
child.measure(MeasureSpec.makeMeasureSpec((mainParent.getWidth() / 2) - 30, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((mainParent.getHeight() / 2) - 30, MeasureSpec.EXACTLY));
Log.d("SAMPLE", "RelativeLayout Child childcount: " + child.getChildCount());
for (int i = 0; i < child.getChildCount(); i++) {
Log.d("SAMPLE", "In loop: " + i);
final View childOfChild = child.getChildAt(i);
childOfChild.measure(MeasureSpec.makeMeasureSpec((mainParent.getWidth() / 2) - 30, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(200, MeasureSpec.AT_MOST));
}
You can see here that I treated the child of the first RelativeLayout as another RelativeLayout then made the loop using the child.getChildCount(). Also (I just added default measure for the second RelativeLayout, you can edit it to whichever you prefer). This calls the TextViews properly now as to what you would expect from your code.
Here is a screenshot after running the code.
So overall. I just edited out the onMeasure() part of your code.. The main problem was just with which layout you were referring and looping. :D

Related

How to keep scrolling position in ScrollView after rotating the screen in fragment with LiveDate object loaded

I have a fragment which is to display a lyrics of a song which is loaded from room databse based on its given id.
I'd like to preserve scrolling position after rotating the screen. Now after rotating the song is loaded again from db and the view is on the very top regardless of the scrolling position befor the rotating.
I thought that I can save scrolling position in onSaveInstanceState some bundle in onCreateView() use command on mSongDisplayScrollView.scrollTo(x, y)
The fragment code:
public class SongDisplayFragment extends Fragment {
private Song mSongToDisplay;
private ScrollView mSongDisplayScrollView;
private TextView mSongTitleTextView;
private RecyclerView mSongLyricsRecyclerView;
private GuitarSongbookViewModel mGuitarSongbookViewModel;
public static final String SONG_ID_KEY = "SONG_ID_KEY";
public SongDisplayFragment() {
}
public static SongDisplayFragment newInstance(Long songId) {
SongDisplayFragment songDisplayFragment = new SongDisplayFragment();
Bundle arguments = new Bundle();
arguments.putLong(SONG_ID_KEY,
songDisplayFragment.setArguments(arguments);
return songDisplayFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(false);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_song_display, container, false);
mSongDisplayScrollView = view.findViewById(R.id.song_display_scroll_view);
mSongLyricsRecyclerView = view.findViewById(R.id.lyrics_rv_);
mSongTitleTextView = view.findViewById(R.id.
mGuitarSongbookViewModel = ViewModelProviders.of(this).get(GuitarSongbookViewModel.class);
final SongDisplayAdapter songDisplayAdapter = new SongDisplayAdapter(getContext());
Long songId = null;
if (getArguments().containsKey(SONG_ID_KEY)) {
songId = getArguments().getLong(SONG_ID_KEY);
}
if (songId != null) {
final Long finalSongId = songId;
mGuitarSongbookViewModel.getSongById(songId).observe(this, new Observer<Song>() {
#Override
public void onChanged(#Nullable final Song song) {
mSongToDisplay = song;
mSongTitleTextView.setText(mSongToDisplay.getMTitle());
songDisplayAdapter.setSong(song);
}
});
}
mSongLyricsRecyclerView.setAdapter(songDisplayAdapter);
mSongLyricsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
return view;
}
}
The fragment XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.SongDisplayFragment">
<ScrollView
android:id="#+id/song_display_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/autoscroll_bar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
tools:layout_editor_absoluteX="138dp">
<LinearLayout
android:id="#+id/son_display_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/displayed_song_title_txt_"
style="#style/TitleOfDisplayedSong"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="#string/title_placeholder" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/lyrics_rv_"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
The fragment displays a tiltle and lyrics of a song in a textView and RecyclerView which adapter class code is:
package com.example.guitarsongbook.adapters;
import android.content.Context;
import android.graphics.Rect;
import android.text.Html;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.guitarsongbook.R;
import com.example.guitarsongbook.daos.SongChordJoinDao;
import com.example.guitarsongbook.model.Chord;
import com.example.guitarsongbook.model.Song;
import java.util.ArrayList;
import java.util.List;
public class SongDisplayAdapter extends RecyclerView.Adapter<SongDisplayAdapter.SongViewHolder> {
private Context context;
private final LayoutInflater mInflater;
private Song mSong;
private ArrayList<String> mLyrics;
public SongDisplayAdapter(Context context) {
this.context = context;
mInflater = LayoutInflater.from(context);
}
#NonNull
#Override
public SongViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = mInflater.inflate(R.layout.song_lyrics_rv_item, parent, false);
return new SongDisplayAdapter.SongViewHolder(itemView);
}
public void setSong(Song song){
mSong = song;
mLyrics = mSong.getMLyrics();
notifyDataSetChanged();
}
#Override
public void onBindViewHolder(#NonNull SongViewHolder holder, int position) {
holder.bindTo(position);
}
#Override
public int getItemCount() {
if (mLyrics != null)
return mLyrics.size();
else return 0;
}
public class SongViewHolder extends RecyclerView.ViewHolder {
private final TextView mLyricsLineTextView;
public SongViewHolder(#NonNull View itemView) {
super(itemView);
mLyricsLineTextView = itemView.findViewById(R.id.song_lyric_line_txt_);
}
public void bindTo(int position) {
if (mSong != null) {
mLyricsLineTextView.setText(Html.fromHtml(mLyrics.get(position)));
} else {
mLyricsLineTextView.setText(context.getString(R.string.no_song_label));
}
}
}
}
RecyclerView item XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/song_lyric_line_txt_"
style="#style/LyricOfDisplayedSong"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toStartOf="#+id/song_chord_line_txt_"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="placeholder text" />
</androidx.constraintlayout.widget.ConstraintLayout>
I think that the problem is that after rotating the screen song has to be loaded again by observer's methon onChange() so there is a short moment that there is nothing to display for scrollView. I'd like to find some solution how to return to the old position after again loading the song.
The first step is to persist the values of the objects through your Fragment's onSaveInstanceState method.
More of my explanations would come from the comments within each code block, so you can understand better on how it all works.
Values to be persisted: mSongToDisplay and mScrollViewPosition
//new variable introduced
int mScrollViewPosition = 0;
#Override
protected void onSaveInstanceState(Bundle outState) {
//If screen orientation changes, android redraws all views
//and non persistent data would be lost.
//Nevertheless, before this happens android informs the app via this method
//So to prevent state loss we save the data to a temporary storage
outState.putParcelable("song_data", mSongToDisplay);
//save ScrollView current position
outState.putInt("position_data", mSongDisplayScrollView.getScrollY());
//call super to commit your changes
super.onSaveInstanceState(outState);
}
Next is to recover the data after orientation change is completed
Add the following block in your fragments onCreateView and make sure the if statement block comes after initializing your adapter
...
//...add adapter initialization block here before checking for saved data
//Check for saved data
if (savedInstanceState != null) {
// Retrieve the data you saved
mSongToDisplay = savedInstanceState.getParcelable("song_data");
mScrollViewPosition = savedInstanceState.getInt("position_data");
//Re-initialize and reload adapter record
mSongToDisplay = song;
mSongTitleTextView.setText(mSongToDisplay.getMTitle());
songDisplayAdapter.setSong(song);
}
else {
//No data to retrieve
//initialize data model here.
}
//Assign the adapter to recyclerView
mSongLyricsRecyclerView.setAdapter(songDisplayAdapter);
mSongLyricsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
try{
//Finally we set the scrollview position at this point just to make sure
//recyclerview has its data loaded and full length of scrollview is restored
mSongDisplayScrollView.setScrollY(mScrollViewPosition);}
catch(Exception ex){
//position isn't right
}
return view;
At this point the required data has been persisted and on every screenOrientationChange the app would load the previously saved song object into the adapter preventing a false call to the db or api, and would also scroll to the expected position using the saved integer value mScrollViewPosition.

Hiding ‘Bottom Navigation Bar’ whilst keyboard is present - Android

I have a small demo chat UI application. This application has a bottom navigation bar. I need the bottom navigation bar to hide when the keyboard appears.
Here is an example of the chat UI
As you can see when you click in the EditText element, the keyboard appears but the bottom navigation bar stays visible. I have tried methods such as this measurement method, but the UI elements flicker like this.
Is there a proper way to hide the bottom navigation bar when the keyboard is visible?
EDIT:
In the below activity you can see where I set the keyboard listener to adjust the position of UI elements when the keyboard is determined as being visible.
This is my activity code, uses setKeyboardListener method from the above link and set it in onCreateView:
package uk.cal.codename.projectnedry.TeamChatFragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Layout;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.roughike.bottombar.BottomBar;
import java.util.ArrayList;
import butterknife.BindView;
import butterknife.ButterKnife;
import uk.cal.codename.projectnedry.R;
import uk.cal.codename.projectnedry.TeamChatFragment.ListAdapter.TeamChatListAdapter;
import uk.demo.cal.genericmodelviewpresenter.GenericMvp.GenericMvpFragment;
import static android.view.View.GONE;
/**
* A simple {#link Fragment} subclass.
* Activities that contain this fragment must implement the
* {#link TeamChatView.OnFragmentInteractionListener} interface
* to handle interaction events.
* Use the {#link TeamChatView#newInstance} factory method to
* create an instance of this fragment.
*/
public class TeamChatView extends GenericMvpFragment implements TeamChatContract.RequiredViewOps {
private OnFragmentInteractionListener mListener;
#BindView(R.id.teamChatList)
RecyclerView mTeamChatRecyclerView;
#BindView(R.id.teamChatSendButton)
ImageButton mTeamChatSendButton;
#BindView(R.id.messageTextInput)
EditText mMessageTextInput;
TeamChatListAdapter mTeamChatListAdapter;
TeamChatListAdapter.ClickListener mTeamChatListClickListener;
private ArrayList<String> mTestMessageList;
public interface OnKeyboardVisibilityListener {
void onVisibilityChanged(boolean visible);
}
public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
private boolean wasOpened;
private final int DefaultKeyboardDP = 100;
// From #nathanielwolf answer... Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
private final Rect r = new Rect();
#Override
public void onGlobalLayout() {
// Convert the dp to pixels.
int estimatedKeyboardHeight = (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, activityRootView.getResources().getDisplayMetrics());
// Conclude whether the keyboard is shown or not.
activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
boolean isShown = heightDiff >= estimatedKeyboardHeight;
if (isShown == wasOpened) {
Log.d("Keyboard state", "Ignoring global layout change...");
return;
}
wasOpened = isShown;
listener.onVisibilityChanged(isShown);
}
});
}
public TeamChatView() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #return A new instance of fragment TeamChatView.
*/
public static TeamChatView newInstance() {
TeamChatView fragment = new TeamChatView();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
#SuppressLint("MissingSuperCall")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(TeamChatPresenter.class, TeamChatModel.class, savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
final View view = inflater.inflate(R.layout.fragment_team_chat_view, container, false);
this.mUnbinder = ButterKnife.bind(this, view);
mTestMessageList = new ArrayList<>();
this.mTeamChatListAdapter = new TeamChatListAdapter(mTestMessageList);
this.mTeamChatRecyclerView.setAdapter(this.mTeamChatListAdapter);
final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
this.mTeamChatRecyclerView.setLayoutManager(linearLayoutManager);
this.mTeamChatSendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!String.valueOf(mMessageTextInput.getText()).equals("")) {
getSpecificImpOfGenericPresenter().sendMessage(String.valueOf(mMessageTextInput.getText()));
mMessageTextInput.setText("");
mTeamChatRecyclerView.smoothScrollToPosition(mTestMessageList.size());
}
}
});
setKeyboardListener(new OnKeyboardVisibilityListener(){
#Override
public void onVisibilityChanged(boolean visible) {
RelativeLayout contentFrame = (RelativeLayout) getActivity().findViewById(R.id.content_company_navigation);
BottomBar lowerNavigationBar = (BottomBar) getActivity().findViewById(R.id.bottomBar);
if (visible) { // if more than 100 pixels, its probably a keyboard...
lowerNavigationBar.setVisibility(GONE);
contentFrame.setPadding(0, 0, 0, 0);
mTeamChatRecyclerView.smoothScrollToPosition(mTestMessageList.size());
} else {
contentFrame.setPadding(0, 0, 0, convertDpToPixel(60, getContext()));
mTeamChatRecyclerView.smoothScrollToPosition(mTestMessageList.size());
lowerNavigationBar.setVisibility(View.VISIBLE);
}
}
});
return view;
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*
* #param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
* #param context Context to get resources and device specific display metrics
* #return A float value to represent px equivalent to dp depending on device density
*/
public static int convertDpToPixel(float dp, Context context){
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
int px = (int) (dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
return px;
}
public void addToTestMessageList(String str){
this.mTestMessageList.add(str);
this.mTeamChatListAdapter.notifyDataSetChanged();
}
#Override
public void onDestroyView() {
super.onDestroyView();
// getView().getViewTreeObserver().removeGlobalOnLayoutListener(test);
}
#Override
public TeamChatPresenter getSpecificImpOfGenericPresenter() {
return (TeamChatPresenter) this.mPresenter;
}
}
This is my XML layout:
<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"
android:orientation="vertical"
tools:context="uk.cal.codename.projectnedry.TeamChatFragment.TeamChatView">
<android.support.v7.widget.RecyclerView
android:layout_above="#+id/chatViewMessageEntryLayout"
android:id="#+id/teamChatList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:isScrollContainer="false"
android:paddingTop="10dp"
android:scrollbars="vertical" />
<RelativeLayout
android:id="#+id/chatViewMessageEntryLayout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<View
android:id="#+id/chatViewMessageEntrySeperator"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e3e3e8" />
<EditText
android:id="#+id/messageTextInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_below="#+id/chatViewMessageEntrySeperator"
android:layout_toLeftOf="#+id/teamChatSendButton"
android:background="#android:color/transparent"
android:hint="Enter message"
android:inputType="textCapSentences|textMultiLine"
android:maxLength="1000"
android:maxLines="4"
android:paddingLeft="10dp" />
<ImageButton
android:id="#+id/teamChatSendButton"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_gravity="center"
android:background="#00B9EF"
android:src="#drawable/ic_send_white_24dp" />
</RelativeLayout>
</RelativeLayout>
The easiest implementation, Add AndroidManifest.xml in
<activity android:windowSoftInputMode="adjustPan"/>
hopefully this helps someone out. Enjoy !
you just add this code in your manifest like this way..
<activity android:name=".MainActivity"
android:windowSoftInputMode="adjustPan">
this works for me.. happy coding
I ended up using the height measuring method that seems to be the standard way of soft keyboard detection which is described in this answer. However, I used this library's implementation of it, as it is still the same ViewTreeObserver.OnGlobalLayoutListener method, implemented well, and allowed me to abstract the code out of my applications main codebase.
When this keyboard visibility listener is triggered, I then hide/show the bottom navigation bar (which I have explained here).
Add this line in onResume() of your Activity or Fragment.
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
It is worked for me. Just try it once.
Just add the attribute below to every activity in your AndroidManifest.xml file.
<activity
android:name=".MainActivity"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan" />
Actively listen for the Keyboard(IME) open/close events and show/hide bottom navigation accordingly.
We can make use of the WindowInsetsCompat API which makes this job effortless.
Step 1: Make sure you have migrated to AndroidX
Step 2: In your Fragment inside onCreateView add the listener for Keyboard(IME) events
ViewCompat.setOnApplyWindowInsetsListener(window.decorView.rootView) { _, insets ->
//This lambda block will be called, every time keyboard is opened or closed
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
if(imeVisible){
//Now show-hide bottom navigation accordingly
}
insets
}
This is all you need🎉
For more info on window insets, you can check out this in-depth article
NOTE:Earlier detecting Keyboard open/close events was not an easy task. Android Devs resorted to all types of hacks to accomplish this. But after decades of requests, prayers and rants Google finally came up with WindowInsets API. Thank you, Google🙏🏻
private boolean keyboardListenersAttached = false;
private ViewGroup rootLayout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_settings, container, false);
rootLayout = (ViewGroup) view.findViewById(R.id.settings_layout);
attachKeyboardListeners();
return view;
}
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new
ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int heightDiff = rootLayout.getRootView().getHeight() -
rootLayout.getHeight();
if (getActivity() != null) {
int contentViewTop =
getActivity().getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
LocalBroadcastManager broadcastManager =
LocalBroadcastManager.getInstance(getContext());
Rect r = new Rect();
rootLayout.getWindowVisibleDisplayFrame(r);
int screenHeight = rootLayout.getRootView().getHeight();
// r.bottom is the position above soft keypad or device button.
// if keypad is shown, the r.bottom is smaller than that before.
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight > screenHeight * 0.15) {
onHideKeyboard();
Intent intent = new Intent("KeyboardWillHide");
broadcastManager.sendBroadcast(intent);
} else {
int keyboardHeight = heightDiff - contentViewTop;
onShowKeyboard(keyboardHeight);
Intent intent = new Intent("KeyboardWillShow");
intent.putExtra("KeyboardHeight", keyboardHeight);
broadcastManager.sendBroadcast(intent);
}
}else {
}
}
};
protected void onShowKeyboard(int keyboardHeight) {
System.out.println("keyboard is shown");
dashboardActivity.bottomNavigationView.setVisibility(View.VISIBLE);
dashboardActivity.fab.setVisibility(View.VISIBLE);
}
protected void onHideKeyboard() {
System.out.println("keyboard is hide");
dashboardActivity.bottomNavigationView.setVisibility(View.INVISIBLE);
dashboardActivity.fab.setVisibility(View.INVISIBLE);
}
protected void attachKeyboardListeners() {
if (keyboardListenersAttached) {
return;
}
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
keyboardListenersAttached = true;
}
in Manifest.xml file add
<activity
android:name=".Activity.DashboardActivity"
android:windowSoftInputMode="adjustResize"
/>
For API 21+:
Firstly, set "android:windowSoftInputMode" for the activity to "adjustResize" in AndroidManifest.xml
Secondly, in your acticity's onCreate method register the following listener:
window.decorView.setOnApplyWindowInsetsListener { view, insets ->
val insetsCompat = toWindowInsetsCompat(insets, view)
val isImeVisible = insetsCompat.isVisible(WindowInsetsCompat.Type.ime())
// below line, do the necessary stuff:
dataBinding.bottomNavigation.visibility = if (isImeVisible) View.GONE else View.VISIBLE
view.onApplyWindowInsets(insets)
}
Add the attribute : android:windowSoftInputMode="adjustResize"" in your manifest inside activity tag:
<activity
android:name=".YourActivity"
android:windowSoftInputMode="adjustResize"/>
NB: I suggest, You should use NestedScrollView as the parent layout.
Hope this helps.
In my case, I am using DrawerLayout as a parent view with some layout content and A Navigation Bottom Bar.
In Manifest file add "android:windowSoftInputMode="adjustPan" this TAG with Activity and this working fine.
<activity android:name=".Activities.OrderSceenWithOrder"
android:windowSoftInputMode="adjustPan"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
That answer might be helpful for people still looking for solution.
Bottom Navigation bar moves up with keyboard
Add this onResume() in your fragment
#Override
public void onResume() {
Objects.requireNonNull(getActivity()).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
super.onResume();
}
Hope this helps someone!
That solution works for me and it also doesn't show overlapped bottom navbar
just add this line to your activity:
android:windowSoftInputMode="adjustResize|adjustPan"

Android App OnClick Crashing

So i have a simple app, just a menu with a few buttons, when a button is clicked you are brought to a new page. The page has a button, which when clicked, keeps changing its background image until it runs out of images (list of image names stored in strings), then you are brought back to the main menu. I can do this twice, then on the third attempt, if i click a button on the menu the app crashes. This doesnt happen on the emulator, only when i run it on my phone. I dont know why this is happening
package com.example.otapp;
import com.example.otapp.R.raw;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.media.MediaPlayer;
public class MainActivity extends ActionBarActivity {
public static String DPExtension;//Holds the letters dp
public String list;
public static int Screen_Height;//holds screen height
public static int Screen_Width;//holds screen width
public Intent intent;
public MediaPlayer audio;
public final static String EXTRA_MESSAGE = "com.example.otapp.MESSAGE";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//this code block is for getting the screen proportions
Display getdisplay = getWindowManager().getDefaultDisplay();
DisplayMetrics dispMetrics = new DisplayMetrics();
getdisplay.getMetrics(dispMetrics);
float densitydp = getResources().getDisplayMetrics().density;
float ScreenHeightdp = dispMetrics.heightPixels / densitydp;
float ScreenWidthdp = dispMetrics.widthPixels / densitydp;
//Below dimension value holders do not use pixel density
float ScreenHeightCheck = dispMetrics.heightPixels;
float ScreenWidthCheck = dispMetrics.heightPixels;
DPExtension = "dp";
Screen_Height = (int) ScreenHeightCheck;
Screen_Width = (int) ScreenWidthCheck;
//The printlns are so I can discern the outputs in LogCat
//System.out.println("Screen Height:" + Screen_Height);
//System.out.println("Screen Width:" + Screen_Width);
View Button1 = findViewById(R.id.Button01);
LinearLayout.LayoutParams params = (LayoutParams) Button1.getLayoutParams();
params.height = Screen_Height/3;
Button1.setLayoutParams(params);
View Button2 = findViewById(R.id.Button02);
Button2.setLayoutParams(params);
View Button3 = findViewById(R.id.Button03);
Button3.setLayoutParams(params);
View Button4 = findViewById(R.id.Button04);
Button4.setLayoutParams(params);
Button1.setOnClickListener(onClickListener);
Button2.setOnClickListener(onClickListener);
Button3.setOnClickListener(onClickListener);
Button4.setOnClickListener(onClickListener);
Button1.setBackgroundResource(getResources().getIdentifier("gettingup", "drawable", getPackageName()));
intent = new Intent(this, DisplayMessageActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
audio = MediaPlayer.create(MainActivity.this, raw.buttonsound);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private OnClickListener onClickListener = new OnClickListener() {
#Override
public void onClick(View v) {
// play sound
audio.start();
// do different things for each different button
switch(v.getId()) {
case R.id.Button01:
list = "Get Up";
intent.putExtra(EXTRA_MESSAGE, list);
startActivity(intent);
break;
case R.id.Button02:
list = "Get Dressed";
intent.putExtra(EXTRA_MESSAGE, list);
startActivity(intent);
break;
case R.id.Button03:
list = "Get Dressed";
intent.putExtra(EXTRA_MESSAGE, list);
startActivity(intent);
break;
case R.id.Button04:
list = "Get Dressed";
intent.putExtra(EXTRA_MESSAGE, list);
startActivity(intent);
break;
}
}
};
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent" android:background="#1E90FF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="#+id/Button01"
android:layout_width="match_parent"
android:layout_height="125dp"
android:text="#string/button_send"/>
<Button
android:id="#+id/Button03"
android:layout_width="match_parent"
android:layout_height="125dp"
android:text="#string/button2_send" />
<Button
android:id="#+id/Button04"
android:layout_width="match_parent"
android:layout_height="125dp"
android:text="#string/button3_send" />
<Button
android:id="#+id/Button02"
android:layout_width="match_parent"
android:layout_height="125dp"
android:text="#string/button4_send" />
</LinearLayout>
</ScrollView>
If the source above is formatted correctly, line 99, which is where the NullPointerException is thrown, is:
audio.start();
This means that audio is null. It is declared on line 83:
audio = MediaPlayer.create(MainActivity.this, raw.buttonsound);
Most likely what's happening is that the MediaPlayer.create is unable to located the raw.buttonsound. I'd debug at this line and verify that the MediaPlayer is failing to create the audio.
Try moving the definition of the onClickListener object into the onCreate method.
You can declare it as a member variable, but you should create the object and assign it to the field in onCreate()

android: null pointer exception when trying to set any property of button

i have checked everything in this but still i am getting a null pointer exception . whenever i am trying to change the properties of button in java file, app stop working and logcat showing null pointer exception. plzz help me out
here is my code
package com.example.rapid_finger;
import java.util.Random;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
public class PlayScreen extends Activity {
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Random rand = new Random();
int data = rand.nextInt(99);
String str = Integer.toString(data);
Button b = (Button) findViewById(R.id.b1);
b.setText(str);
setContentView(R.layout.activity_play_screen);
// Show the Up button in the action bar.
setupActionBar();
}
/**
* Set up the {#link android.app.ActionBar}, if the API is available.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void setupActionBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.play_screen, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up- vs-back
//
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
and here is my xml file
<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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".PlayScreen" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/scores" />
<Button
android:id="#+id/b1"
android:background="#drawable/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
You did findViewById before you setContentView.
setContentView should come first
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_screen);
Random rand = new Random();
int data = rand.nextInt(99);
String str = Integer.toString(data);
Button b = (Button) findViewById(R.id.b1);
b.setText(str);
// Show the Up button in the action bar.
setupActionBar();
}
put setContentView(R.layout.activity_play_screen); after super.onCreate(savedInstanceState);
// replace this code
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play_screen);
Random rand = new Random();
int data = rand.nextInt(99);
String str = Integer.toString(data);
Button b = (Button) findViewById(R.id.b1);
b.setText(str);
// Show the Up button in the action bar.
setupActionBar();
}

Android ScrollView only scroll to bottom if you're already there

I have a basic android app set up with a scroll view and text added to it dynamically.
I want it to scroll to the bottom when text is added (which already happens) but I only want it to scroll to the bottom if you are already at the bottom, so if you're reading something it doesn't just scroll.
Here's what I have so far.
private void AddText(final String msg){
this.runOnUiThread(new Runnable() {
public void run() {
TextView log = (TextView) findViewById(R.id.chatlog);
if(log.getText().equals("Loading...")){
log.setText(msg);
}else{
ScrollView scroller = (ScrollView) findViewById(R.id.scroll_container);
//set current scroll position
int scroll_pos = scroller.getScrollY();
//scroll to bottom
scroller.fullScroll(ScrollView.FOCUS_DOWN);
//set bottom position
int scroll_bot = scroller.getScrollY();
//add the text
log.append("\r\n" + msg);
//if you weren't at the bottom
//scroll back to where you were.
//This isn't working, scroll bot is the same
//as scroll pos.
if(scroll_pos != scroll_bot){
scroller.scrollTo(0, scroll_pos);
}
//System.out.println("Pos: " + scroll_pos);
//System.out.println("Bot: " + scroll_bot);
}
}
});
}
The best solution I found so far is to use scrollView.post() method with a runnable that will be invoked after text change:
final ScrollView scrollView = (ScrollView) findViewById(R.id.consoleTab);
TextView textView = (TextView) findViewById(R.id.consoleView);
boolean autoScroll = (textView.getBottom() - (scrollView.getHeight() + scrollView.getScrollY())) <= 0;
textView.setText(state.getConsole().getText());
if (autoScroll) {
scrollView.post(new Runnable() {
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
Here is a working example I got running in the emulator and on my galaxy s.
Basically there are two buttons to add text to the bottom text view, depending on your devices size only the second of these should be usable to see the autoscrolling. The textview being edited uses the ontextchangedlistener to check the scroll position before its text is changed and then to call a delayed (so the screen can update with the additional text) autoscroll where appropriate, after the text has changed.
The layout xml is as follows:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scrollmain">
<RelativeLayout
android:id="#+id/mainRelative"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to the scrolling test application!" />
<Button
android:id="#+id/firstTextAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/titleText"
android:text="Click me to add text to the textview below without scrolling"/>
<Button
android:id="#+id/secondTextAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/firstTextAddButton"
android:layout_marginTop="380dp"
android:text="Click me to add text to the textview below and scroll (if you are currently scrolled all the way to the bottom)"/>
<TextView
android:id="#+id/textToEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/secondTextAddButton"
android:textSize="18sp"
android:text="Some text to get us started."/>
/>
</RelativeLayout>
</ScrollView>
And the code for the activity is as follows:
package code.example.scrollingontextchange;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
public class MainActivity extends Activity {
private int scroll_pos;
private int maxScrollPosition;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView)findViewById(R.id.textToEdit);
tv.addTextChangedListener(new TextWatcher(){
#Override
public void afterTextChanged(Editable s) {
if(scroll_pos == maxScrollPosition)
{
Handler h = new android.os.Handler()
{
#Override
public void handleMessage(android.os.Message msg)
{
switch(msg.what)
{
case 0 :
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ScrollView scrll = (ScrollView)findViewById(R.id.scrollmain);
scrll.fullScroll(ScrollView.FOCUS_DOWN);
break;
default :
break;
}
}
};
h.sendEmptyMessage(0);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
ScrollView scrll = (ScrollView)findViewById(R.id.scrollmain);
RelativeLayout rl = (RelativeLayout)findViewById(R.id.mainRelative);
scroll_pos = scrll.getScrollY();
maxScrollPosition = rl.getHeight() - scrll.getHeight();
}
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// TODO Auto-generated method stub
}
});
OnClickListener addTextOnClick = new OnClickListener() {
#Override
public void onClick(View v) {
TextView tv = (TextView)findViewById(R.id.textToEdit);
tv.setText(tv.getText() + "\nA long time ago, in a galaxy s far far away............");
}
};
Button b = (Button)findViewById(R.id.firstTextAddButton);
b.setOnClickListener(addTextOnClick);
Button b2 = (Button)findViewById(R.id.secondTextAddButton);
b2.setOnClickListener(addTextOnClick);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Hope this helps.
Don't ask me why, but setting your TextView's gravity to bottom it does exactly what you want.

Categories