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"
Related
I'm new to Android and have been learning about Android app development for about a month just through a book, and right now, I'm working on my own project, which is a basic app that manages user tasks and schedule. There is this one problem I am having. I am trying to add a new task to a RecyclerView list that contains the list of main tasks, but I keep getting errors. Here is what I have (I don't want to show everything because it is a project):
The RecyclerView list (main_task_window.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/MainTaskList"
android:layout_width="match_parent"
android:layout_height="449dp"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="0dp"
android:layout_marginTop="1dp"
android:layout_marginBottom="61dp" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/AddaMainTask"
android:onClick="addnewtask"
android:layout_width="54dp"
android:layout_height="51dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="13dp"
android:layout_marginBottom="10dp"
android:clickable="true"
app:srcCompat="#android:drawable/ic_input_add" />
</RelativeLayout>
The window that allows users to add new main tasks as well as additional notes (add_new_main_task_window.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/maintasks"
android:layout_width="match_parent"
android:layout_height="62dp"
android:hint="Main Task"/>
<EditText
android:id="#+id/notes"
android:layout_width="match_parent"
android:layout_height="62dp"
android:hint="Additional notes"/>
<Button
android:id="#+id/addButton"
android:onClick="sendButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add to main task" />
</LinearLayout>
Now here are the following Java files:
1. MainTasks.java (contains the get and set methods for the main task and additional notes)
package com.ricstudios.daymanager;
public class MainTasks
{
private String mt, an; //'mt' stands for "main tasks". 'an' stands for "additional notes"
//MainTasks class constructor
public MainTasks(String mt, String an)
{
//mt and an in the parameters is equal to the mt and an variables above
this.mt = mt;
this.an = an;
}
public String getMainTasks() //this get method obtains the main task string input)
{
return mt;
}
public void setMainTasks(String MainTasks) //this set methods stores the main task string input)
{
this.mt = MainTasks;
}
public String getAdditionalNotes() //this get method obtains the main task string input
{
return an;
}
public void setAdditionalNotes(String AddNotes) //this set method stores the main task string
{
this.an = AddNotes;
}
}
MainTaskAdapter.java (contains the adapter to render the data)
package com.ricstudios.daymanager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import java.util.List;
public class MainTaskAdapter extends RecyclerView.Adapter<MainTaskAdapter.ViewHolder>
{
private List<MainTasks> maintasklist; //will contain the main task and additional notes string inputs
public class ViewHolder extends RecyclerView.ViewHolder
{
public EditText maintask, addnotes;
//provides a reference to the views for each data item
public ViewHolder(View view)
{
super(view);
maintask = (EditText)view.findViewById(R.id.maintasks);
addnotes = (EditText)view.findViewById(R.id.notes);
}
}
//MainTaskAdapter class constructor
public MainTaskAdapter(List<MainTasks> maintasklist)
{
this.maintasklist = maintasklist;
}
//create new view (invoked by the layout manager)
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.add_new_main_task_window, parent, false);
return new ViewHolder(itemView);
}
//replaces the contents of the main task view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position)
{
MainTasks obj = maintasklist.get(position); //MainTasks class obj called 'obj', which allows access to the MainTasks class
//obtains the main task and additional notes from the MainTask class
holder.maintask.setText(obj.getMainTasks());
holder.addnotes.setText(obj.getAdditionalNotes());
}
//returns the size of the main task list (invoked by the layout manager)
#Override
public int getItemCount()
{
return maintasklist.size();
}
}
AddNewMainTask.java (adds the tasks and additional notes to the RecyclerView)
package com.ricstudios.daymanager;
import android.support.v7.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.Button;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/*This class handles the window for adding new main tasks to the main task list (this window is the add_new_main_task_window.xml layout)*/
public class AddNewMainTask extends AppCompatActivity
{
private Button addbutton; //Button element from the add_new_main_task_window.xml layout
private EditText maintask, addnotes; //EditText elements from the add_new_main_task_window.xml layout
private List<MainTasks> maintasklist = new ArrayList<>();
private RecyclerView ListofMainTasks; //RecyclerView list element from the add_new_main_task_window.xml layout (containing the Main Task list)
private MainTaskAdapter TAdapter; //MainTaskAdapter class object 'TAdapter', allows access to the MainTaskAdapter class
/*sendButton method from the Button element in the add_new_main_task_window.xml and adds the main task and additional notes to the RecyclerView main task list*/
public void sendButton(View view)
{
maintask = (EditText)view.findViewById(R.id.maintasks);
addnotes = (EditText)view.findViewById(R.id.notes);
ListofMainTasks = (RecyclerView)findViewById(R.id.MainTaskList);
TAdapter = new MainTaskAdapter(maintasklist);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
ListofMainTasks.setLayoutManager(mLayoutManager);
ListofMainTasks.setItemAnimator(new DefaultItemAnimator());
ListofMainTasks.setAdapter(TAdapter);
/*Passes the EditText element values into the MainTask.java parameters*/
MainTasks mnt = new MainTasks(maintask.getText().toString(), addnotes.getText().toString());
maintasklist.add(mnt);
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.add_new_main_task_window); //displays the window that allows users to add new main tasks (which is the add_new_main_task_window.xml layout)
addbutton = (Button)findViewById(R.id.addButton);
/*When user clicks button, the values the user puts in the EditText fields will be added to the RecyclerView list of the main_task_window.xml layout*/
addbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v)
{
sendButton(v);
}
});
}
}
Whenever I press the "Add to Main Task" button, the app suddenly stop. Please help. Oh, and about the floatingactionbutton in the main_task_window.xml, don't worry about that. That just sends the user to the add_new_main_task_window.xml
Update: 3/5/19
I'm getting this error from the logcat:
03-05 15:22:49.298 10335-10335/com.project.daymanager E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.project.daymanager, PID: 10335
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.setLayoutManager(android.support.v7.widget.RecyclerView$LayoutManager)' on a null object reference
at com.project.daymanager.AddNewMainTask.sendButton(AddNewMainTask.java:39)
at com.project.daymanager.AddNewMainTask$1.onClick(AddNewMainTask.java:63)
at android.view.View.performClick(View.java:5702)
at android.widget.TextView.performClick(TextView.java:10887)
at android.view.View$PerformClick.run(View.java:22541)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Replace
view.findViewById();
with
findViewById();
In the MainTasks.java Don't use a constructor
public MainTasks(String mt, String an)
Just keep the set And get methods and do
MainTasks mt = new MainTasks();
mt.setMainTasks("Title");
mt.setAdditionalNotes("Note");
list.add(mt);
If it still not working, please post the error log so we can know whats the error.
Update: 3/5/19
You are trying to access the recyclerView of the MainTaskActivity in
ListofMainTasks = (RecyclerView)findViewById(R.id.MainTaskList);
from out side the activity (From another Activity) and that's not possible this way,
you can set the RecyclerView in the MainTaskActivity to static and use it in AddNewMainTask Activity by calling it
MainTaskActivity.myRecyclerView.
Problem is in this line:
ListofMainTasks = (RecyclerView)findViewById(R.id.MainTaskList);
program is unable to find recycler view and its null. and you can't set layout manager to a null object. I've reviewed your code and thing you did wrong is that in you onCreate method you are setting layout setContentView(R.layout.add_new_main_task_window); and you're trying to find recylerview which is not in this layout.
Your recycler view is in main_task_window.xml. use setContentView(R.layout.main_task_window.xml); and your activity will find recyclerview easily.
Note: if you want button/controls also which are in this layout add_new_main_task_window.xml then please move them in layout with recyclerview.
I'm developing an Android app that has to simulate a sort of Pokédex.
For now, what I want to do is simply have all 151 Pokémon printed on my device, so I can scroll them up and down.
The problem is that when I try this thing with such as 9 or 12 images there are no problems, but when I load all the 151 images (all .png), Android kills the app because it's draining too much system resources.
I've heard that there are Java methods that can (don't know how) "destroy" an object when it goes out of the display and then recreate it when it returns in the screen. Anyway if you have different suggestions on how to resolve my problem, every idea is welcome!
Here is my MainActivity:
package com.example.thefe.newsmartkedex;
import android.media.AudioManager;
import android.media.SoundPool;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new ImageAdapter(this));
gridview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
Toast.makeText(MainActivity.this, "" + position,
Toast.LENGTH_SHORT).show();
}
});
};
}
And here is my ImageAdapter class I use for Gridview:
package com.example.thefe.newsmartkedex;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
public class ImageAdapter extends BaseAdapter {
private Context mContext;
public ImageAdapter(Context c) {
mContext = c;
}
public int getCount() {
return mThumbIds.length;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
// if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(200, 200));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds[position]);
return imageView;
}
// references to our images
private Integer[] mThumbIds = {
R.drawable.pkmn1, R.drawable.pkmn2,
R.drawable.pkmn3, R.drawable.pkmn4,
R.drawable.pkmn5, R.drawable.pkmn6,
R.drawable.pkmn7, R.drawable.pkmn8,
R.drawable.pkmn9, R.drawable.pkmn10,
R.drawable.pkmn11, R.drawable.pkmn12,
R.drawable.pkmn13, R.drawable.pkmn14,
R.drawable.pkmn15, R.drawable.pkmn16,
R.drawable.pkmn17, R.drawable.pkmn18,
R.drawable.pkmn19, R.drawable.pkmn20,
R.drawable.pkmn21, R.drawable.pkmn22,
R.drawable.pkmn23, R.drawable.pkmn24,
R.drawable.pkmn25, R.drawable.pkmn26,
R.drawable.pkmn27, R.drawable.pkmn28,
R.drawable.pkmn29, R.drawable.pkmn30,
R.drawable.pkmn31, R.drawable.pkmn32,
R.drawable.pkmn33, R.drawable.pkmn34,
R.drawable.pkmn35, R.drawable.pkmn36,
R.drawable.pkmn37, R.drawable.pkmn38,
R.drawable.pkmn39, R.drawable.pkmn40,
R.drawable.pkmn41, R.drawable.pkmn42,
R.drawable.pkmn43, R.drawable.pkmn44,
R.drawable.pkmn45, R.drawable.pkmn46,
R.drawable.pkmn47, R.drawable.pkmn48,
R.drawable.pkmn49, R.drawable.pkmn50,
R.drawable.pkmn51, R.drawable.pkmn52,
R.drawable.pkmn53, R.drawable.pkmn54,
R.drawable.pkmn55, R.drawable.pkmn56,
R.drawable.pkmn57, R.drawable.pkmn58,
R.drawable.pkmn59, R.drawable.pkmn60,
R.drawable.pkmn61, R.drawable.pkmn62,
R.drawable.pkmn63, R.drawable.pkmn64,
R.drawable.pkmn65, R.drawable.pkmn66,
R.drawable.pkmn67, R.drawable.pkmn68,
R.drawable.pkmn69, R.drawable.pkmn70,
R.drawable.pkmn71, R.drawable.pkmn72,
R.drawable.pkmn73, R.drawable.pkmn74,
R.drawable.pkmn75, R.drawable.pkmn76,
R.drawable.pkmn77, R.drawable.pkmn78,
R.drawable.pkmn79, R.drawable.pkmn80,
R.drawable.pkmn81, R.drawable.pkmn82,
R.drawable.pkmn83, R.drawable.pkmn84,
R.drawable.pkmn85, R.drawable.pkmn86,
R.drawable.pkmn87, R.drawable.pkmn88,
R.drawable.pkmn89, R.drawable.pkmn90,
R.drawable.pkmn91, R.drawable.pkmn92,
R.drawable.pkmn93, R.drawable.pkmn94,
R.drawable.pkmn95, R.drawable.pkmn96,
R.drawable.pkmn97, R.drawable.pkmn98,
R.drawable.pkmn99, R.drawable.pkmn100,
R.drawable.pkmn101, R.drawable.pkmn102,
R.drawable.pkmn103, R.drawable.pkmn104,
R.drawable.pkmn105, R.drawable.pkmn106,
R.drawable.pkmn107, R.drawable.pkmn108,
R.drawable.pkmn109, R.drawable.pkmn110,
R.drawable.pkmn111, R.drawable.pkmn112,
R.drawable.pkmn113, R.drawable.pkmn114,
R.drawable.pkmn115, R.drawable.pkmn116,
R.drawable.pkmn117, R.drawable.pkmn118,
R.drawable.pkmn119, R.drawable.pkmn120,
R.drawable.pkmn121, R.drawable.pkmn122,
R.drawable.pkmn123, R.drawable.pkmn124,
R.drawable.pkmn125, R.drawable.pkmn126,
R.drawable.pkmn127, R.drawable.pkmn128,
R.drawable.pkmn129, R.drawable.pkmn130,
R.drawable.pkmn131, R.drawable.pkmn132,
R.drawable.pkmn133, R.drawable.pkmn134,
R.drawable.pkmn135, R.drawable.pkmn136,
R.drawable.pkmn137, R.drawable.pkmn138,
R.drawable.pkmn139, R.drawable.pkmn140,
R.drawable.pkmn141, R.drawable.pkmn142,
R.drawable.pkmn143, R.drawable.pkmn144,
R.drawable.pkmn145, R.drawable.pkmn146,
R.drawable.pkmn147, R.drawable.pkmn148,
R.drawable.pkmn149, R.drawable.pkmn150,
R.drawable.pkmn151
};
}
Finally, this is the XML file
<?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:id="#+id/activity_main"
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="com.example.thefe.newsmartkedex.MainActivity">
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="90dp"
android:numColumns="auto_fit"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:stretchMode="columnWidth"
android:gravity="center"
/>
</RelativeLayout>
Thanks for help!
First of all, I don't see any violation in your code so let's focus the pictures.
The problem is that when I try this thing with such as 9 or 12 images there are no problems, but when I load all the 151 images (all .png), Android kills the app because it's draining too much system resources.
What do you mean by 'load all the 151 images'? At the same time in the same screen? Or you just make quite a small numbers of them(like 9,12,16 etc.) seen in the view and others out of the screen?
I've heard that there are Java methods that can (don't know how) "destroy" an object when it goes out of the display and then recreate it when it returns in the screen. Anyway if you have different suggestions on how to resolve my problem, every idea is welcome!
You can't simply destroy an object by yourself and the Jvm will handle this for you when your objects are invalid or unused. As to this problem the recreation of objects that you implements in 'getView' seems no harm.
My question is: how many images did you show in one whole screen? And what size of them?
If you can provide your demo here, it will be the best to work on.
Display limited images that can fit your screen and load the other images when scrolling Gridview.
That way system wont have do to do lots of work at once. Your application gets faster also.
You can refer this link:
i want grid view with loading by scroll i have image fetch from sever but i want only 10 images view other can load when scrolling grid view
I need your help on this. I am going to make an app using ViewPager and I since I never been programming for android before I thought it would be good to make a sample app first. I want to use the ViewPager a little bit differently than the classic list-of-items-style so I made an app that will show all the colors (or every 10th color) from #000000 to #FFFFFF.
It doesn't work. I've started the app on the emulator but I just get a white screen. If the default position of the ViewPager when starting is 0 then the color should be black. And when I try to make a breakpoint the program doesn't stop, or it's never reaching the point. I'm using eclipse.
These are the files of the project
MainActivity.java
package com.example.colorswipe;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
public class MainActivity extends Activity {
private ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPager=(ViewPager)this.findViewById(R.id.pager);
mPager.setAdapter(new MyAdapter());
}
private class MyAdapter extends PagerAdapter {
#Override
public int getCount() {
return 0xFFFFFF/10;
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
// TODO Auto-generated method stub
return false;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ColorView view=new ColorView(container.getContext());
view.setBackgroundColor(android.graphics.Color.parseColor(String.format("#%06X", position*10)));
view.setText(position);
container.addView(view);
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View)object);
}
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
ColorView.java
package com.example.colorswipe;
import android.content.Context;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ColorView extends LinearLayout {
private TextView tv;
public ColorView(Context context) {
super(context);
LayoutParams params=new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
this.setLayoutParams(params);
TextView tv=new TextView(context);
this.tv=tv;
this.addView(tv);
}
public void setText(int position) {
tv.setText(Integer.toString(position).toCharArray(), 0, Integer.toString(position).length());
}
}
EDIT - the view pager is there , but I think you don't resolve the color correctly.
I found the problem. isViewFromObject must be implemented as return arg0==arg1.
Problem
I have a very simple activity with two tabs, and I'm trying to handle keyboard input in a custom view. This works great... until I swap tabs. Once I swap tabs, I can never get the events to capture again. In another application, opening a Dialog and then closing it, however, would allow my key events to go through. Without doing that I've found no way of getting my key events again.
What's the issue here? I can't find any way to get key events once I swap tabs, and am curious what's eating them. This example is pretty short and to the point.
Code
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<FrameLayout
android:id="#+id/actionbar_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<view
class="com.broken.keyboard.KeyboardTestActivity$MyView"
android:background="#777777"
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<requestFocus/>
</view>
</LinearLayout>
KeyboardTestActivity.java
package com.broken.keyboard;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;
import android.content.Context;
public class KeyboardTestActivity extends Activity {
public static class MyView extends View {
public void toggleKeyboard()
{ ((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0); }
public MyView(Context context)
{ super(context); }
public MyView(Context context, AttributeSet attrs)
{ super(context, attrs); }
public MyView(Context context, AttributeSet attrs, int defStyle)
{ super(context, attrs, defStyle); }
// FIRST PLACE I TRY, WHERE I WANT TO GET THE PRESSES
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.i("BDBG", "Key went down in view!");
return super.onKeyDown(keyCode,event);
}
// Toggle keyboard on touch!
#Override
public boolean onTouchEvent(MotionEvent event)
{
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN)
{
toggleKeyboard();
}
return super.onTouchEvent(event);
}
}
// Extremely simple fragment
public class MyFragment extends Fragment {
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment, container, false);
return v;
}
}
// Simple tab listener
public static class MyTabListener implements ActionBar.TabListener
{
private FragmentManager mFragmentManager=null;
private Fragment mFragment=null;
private String mTag=null;
public MyTabListener(FragmentManager fragmentManager, Fragment fragment,String tag)
{
mFragmentManager=fragmentManager;
mFragment=fragment;
mTag=tag;
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// do nothing
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
mFragmentManager.beginTransaction()
.replace(R.id.actionbar_content, mFragment, mTag)
.commit();
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
mFragmentManager.beginTransaction()
.remove(mFragment)
.commit();
}
}
FragmentManager mFragmentManager;
ActionBar mActionBar;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Retrieve the fragment manager
mFragmentManager=getFragmentManager();
mActionBar=getActionBar();
// remove the activity title to make space for tabs
mActionBar.setDisplayShowTitleEnabled(false);
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Add the tabs
mActionBar.addTab(mActionBar.newTab()
.setText("Tab 1")
.setTabListener(new MyTabListener(getFragmentManager(), new MyFragment(),"Frag1")));
mActionBar.addTab(mActionBar.newTab()
.setText("Tab 2")
.setTabListener(new MyTabListener(getFragmentManager(), new MyFragment(),"Frag2")));
}
// OTHER PLACE I TRY, DOESN'T WORK BETTER THAN IN THE VIEW
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.i("BDBG", "Key went down in activity!");
return super.onKeyDown(keyCode,event);
}
}
I've solved my own problem, so I thought I'd share the solution. If there's some wording issue, please correct me in a comment; I'm trying to be as accurate as I can but I'm not entirely an android expert. This answer should also serve as an excellent example of how to handle swapping out ActionBar tabs in general. Whether or not one likes the design of the solution code, it should be useful.
The following link helped me figure out my issue: http://code.google.com/p/android/issues/detail?id=2705
Solution
It turns out, there are two important issues at hand. Firstly, if a View is both android:focusable and android:focusableInTouchMode, then on a honeycomb tablet one might expect that tapping it and similar would focus it. This, however, is not necessarily true. If that View happens to also be android:clickable, then indeed tapping will focus the view. If it is not clickable, it will not be focused by touch.
Furthermore, when swapping out a fragment there's an issue very similar to when first instantiating the view for an activity. Certain changes need to be made only after the View hierarchy is completely prepared.
If you call "requestFocus()" on a view within a fragment before the View hierarchy is completely prepared, the View will indeed think that it is focused; however, if the soft keyboard is up, it will not actually send any events to that view! Even worse, if that View is clickable, tapping it at this point will not fix this keyboard focus issue, as the View thinks that it is indeed focused and there is nothing to do. If one was to focus some other view, and then tap back onto this one, however, as it is both clickable and focusable it would indeed focus and also direct keyboard input to this view.
Given that information, the correct approach to setting the focus upon swapping to a tab is to post a runnable to the View hierarchy for the fragment after it is swapped in, and only then call requestFocus(). Calling requestFocus() after the View hierarchy is fully prepared will both focus the View as well as direct keyboard input to it, as we want. It will not get into that strange focused state where the view is focused but the keyboard input is somehow not directed to it, as will happen if calling requestFocus() prior to the View hierarchy being fully prepared.
Also important, using the "requestFocus" tag within the XML of a fragment's layout will most call requestFocus() too early. There is no reason to ever use that tag in a fragment's layout. Outside of a fragment, maybe.. but not within.
In the code, I've added an EditText to the top of the fragment just for testing tap focus change behaviors, and tapping the custom View will also toggle the soft keyboard. When swapping tabs, the focus should also default to the custom view. I tried to comment the code effectively.
Code
KeyboardTestActivity.java
package com.broken.keyboard;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.app.FragmentTransaction;
import android.app.ActionBar.Tab;
import android.content.Context;
public class KeyboardTestActivity extends Activity {
/**
* This class wraps the addition of tabs to the ActionBar,
* while properly swapping between them. Furthermore, it
* also provides a listener interface by which you can
* react additionally to the tab changes. Lastly, it also
* provides a callback for after a tab has been changed and
* a runnable has been post to the View hierarchy, ensuring
* the fragment transactions have completed. This allows
* proper timing of a call to requestFocus(), and other
* similar methods.
*
* #author nacitar sevaht
*
*/
public static class ActionBarTabManager
{
public static interface TabChangeListener
{
/**
* Invoked when a new tab is selected.
*
* #param tag The tag of this tab's fragment.
*/
public abstract void onTabSelected(String tag);
/**
* Invoked when a new tab is selected, but after
* a Runnable has been executed after being post
* to the view hierarchy, ensuring the fragment
* transaction is complete.
*
* #param tag The tag of this tab's fragment.
*/
public abstract void onTabSelectedPost(String tag);
/**
* Invoked when the currently selected tab is reselected.
*
* #param tag The tag of this tab's fragment.
*/
public abstract void onTabReselected(String tag);
/**
* Invoked when a new tab is selected, prior to {#link onTabSelected}
* notifying that the previously selected tab (if any) that it is no
* longer selected.
*
* #param tag The tag of this tab's fragment.
*/
public abstract void onTabUnselected(String tag);
}
// Variables
Activity mActivity = null;
ActionBar mActionBar = null;
FragmentManager mFragmentManager = null;
TabChangeListener mListener=null;
View mContainer = null;
Runnable mTabSelectedPostRunnable = null;
/**
* The constructor of this class.
*
* #param activity The activity on which we will be placing the actionbar tabs.
* #param containerId The layout id of the container, preferable a {#link FrameLayout}
* that will contain the fragments.
* #param listener A listener with which one can react to tab change events.
*/
public ActionBarTabManager(Activity activity, int containerId, TabChangeListener listener)
{
mActivity = activity;
if (mActivity == null)
throw new RuntimeException("ActionBarTabManager requires a valid activity!");
mActionBar = mActivity.getActionBar();
if (mActionBar == null)
throw new RuntimeException("ActionBarTabManager requires an activity with an ActionBar.");
mContainer = activity.findViewById(containerId);
if (mContainer == null)
throw new RuntimeException("ActionBarTabManager requires a valid container (FrameLayout, preferably).");
mListener = listener;
mFragmentManager = mActivity.getFragmentManager();
// Force tab navigation mode
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}
/**
* Simple Runnable to invoke the {#link onTabSelectedPost} method of the listener.
*
* #author nacitar sevaht
*
*/
private class TabSelectedPostRunnable implements Runnable
{
String mTag = null;
public TabSelectedPostRunnable(String tag)
{
mTag=tag;
}
#Override
public void run() {
if (mListener != null) {
mListener.onTabSelectedPost(mTag);
}
}
}
/**
* Internal TabListener. This class serves as a good example
* of how to properly handles swapping the tabs out. It also
* invokes the user's listener after swapping.
*
* #author nacitar sevaht
*
*/
private class TabListener implements ActionBar.TabListener
{
private Fragment mFragment=null;
private String mTag=null;
public TabListener(Fragment fragment, String tag)
{
mFragment=fragment;
mTag=tag;
}
private boolean post(Runnable runnable)
{
return mContainer.post(runnable);
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// no fragment swapping logic necessary
if (mListener != null) {
mListener.onTabReselected(mTag);
}
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
mFragmentManager.beginTransaction()
.replace(mContainer.getId(), mFragment, mTag)
.commit();
if (mListener != null) {
mListener.onTabSelected(mTag);
}
// Post a runnable for this tab
post(new TabSelectedPostRunnable(mTag));
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
mFragmentManager.beginTransaction()
.remove(mFragment)
.commit();
if (mListener != null) {
mListener.onTabUnselected(mTag);
}
}
}
/**
* Simple wrapper for adding a text-only tab. More robust
* approaches could be added.
*
* #param title The text to display on the tab.
* #param fragment The fragment to swap in when this tab is selected.
* #param tag The unique tag for this tab.
*/
public void addTab(String title, Fragment fragment, String tag)
{
// The tab listener is crucial here.
mActionBar.addTab(mActionBar.newTab()
.setText(title)
.setTabListener(new TabListener(fragment, tag)));
}
}
/**
* A simple custom view that toggles the on screen keyboard when touched,
* and also prints a log message whenever a key event is received.
*
* #author nacitar sevaht
*
*/
public static class MyView extends View {
public void toggleKeyboard()
{ ((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, 0); }
public MyView(Context context)
{ super(context); }
public MyView(Context context, AttributeSet attrs)
{ super(context, attrs); }
public MyView(Context context, AttributeSet attrs, int defStyle)
{ super(context, attrs, defStyle); }
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.i("BDBG", "Key (" + keyCode + ") went down in the custom view!");
return true;
}
// Toggle keyboard on touch!
#Override
public boolean onTouchEvent(MotionEvent event)
{
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN)
{
toggleKeyboard();
}
return super.onTouchEvent(event);
}
}
// Extremely simple fragment
public class MyFragment extends Fragment {
#Override
public View onCreateView (LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment, container, false);
return v;
}
}
public class MyTabChangeListener implements ActionBarTabManager.TabChangeListener
{
public void onTabReselected(String tag) { }
public void onTabSelected(String tag) { }
public void onTabSelectedPost(String tag)
{
// TODO: NOTE: typically, one would conditionally set the focus based upon the tag.
// but in our sample, both tabs have the same fragment layout.
View view=findViewById(R.id.myview);
if (view == null)
{
throw new RuntimeException("Tab with tag of (\""+tag+"\") should have the view we're looking for, but doesn't!");
}
view.requestFocus();
}
public void onTabUnselected(String tag) { }
}
// Our tab manager
ActionBarTabManager mActionBarTabManager = null;
// Our listener
MyTabChangeListener mListener = new MyTabChangeListener();
// Called when the activity is first created.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// instantiate our tab manager
mActionBarTabManager = new ActionBarTabManager(this,R.id.actionbar_content,mListener);
// remove the activity title to make space for tabs
getActionBar().setDisplayShowTitleEnabled(false);
// Add the tabs
mActionBarTabManager.addTab("Tab 1", new MyFragment(), "Frag1");
mActionBarTabManager.addTab("Tab 2", new MyFragment(), "Frag2");
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<FrameLayout
android:id="#+id/actionbar_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<!-- note that view is in lower case here -->
<view
class="com.broken.keyboard.KeyboardTestActivity$MyView"
android:id="#+id/myview"
android:background="#777777"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:layout_width="fill_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
I am rather new to Android programming in general and am having particular difficulty with the xml/java UI shuffle... I have a layout which I would like to use as the view displayed when a custom, view class is instantiated in the activity class. This much works fine by simply calling
setContentView(R.layout.mylayout) ;
in the activity or from the custom view class through a handle to the activity. The trouble comes when I wish to interact with the widgets on the layout-- I've tried getting a handle on the buttons with
myButton = (Button) findViewById(R.id.mybuttonid);
and separately with
Button myButton = new Button(contextHandle);
myButton = (Button) findViewById(R.layout.mybuttonid);
but in both cases whenever I try to call any methods from the assumed myButton object I get a NullPointerException in the logcat report; evidently myButton is not properly instantiated in either case given above. What is the proper way to instantiate components of a view in a case like this that combines xml and java so that they can call methods dynamically?
thanks,
CCJ
EDIT: Thanks all for the replies, but I think up to 8/1/2011 the advice has been mostly targeted at an implementation wherein the widgets are to be instantiated in the activity class; I wish to instantiate widgets from an xml layout in a custom view class-- a class completely separate from the activity class which extends View and implements its own OnClickListener interface. Below is my code:
MyActivity Class:
package com.ccg.myactivity;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RadioButton;
public class MyActivity extends Activity implements OnClickListener {
private boolean touched = false;
private RadioButton myRB;
private Button runB;
private CustomView myView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainlayout);
myRB = (RadioButton) findViewById(R.id.testrb);
runB = (Button) findViewById(R.id.goButton);
//set onClick listeners for activity class
runB.setOnClickListener(this);
}
public void onResume(){
super.onResume();
}
public void onClick(View v) {
// do something when the button is clicked
if (myRB.isChecked()){
setContentView(R.layout.mylayout);
myView = new CustomView(this,this); //passing in activity and context
//handles to custom View class
//myView.getAnotherB().setOnClickListener(this); //commented out as we
//don't want to register the custom view's button with the Activty class's
//OnClickListener; instead it should be registered with the custom View class's own
//OnClickListener implementation.
}
else{
Log.d("me","alt click");
}
}
}
CustomView Class:
package com.ccg.myactivity;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import android.view.View.OnClickListener;
public class CustomView extends View implements OnClickListener{
private Button anotherB;
private Context contextHandle;
private Activity actHandle;
public CustomView(Context context, Activity act) {
super(context);
contextHandle = context;
actHandle = act;
//anotherB = new Button(contextHandle); //this shouldn't be necessary for
//instantiation from XML widget
initCustomView();
}
public void initCustomView(){
anotherB = (Button) findViewById(R.id.nextbutton);
anotherB.setOnClickListener(this);
}
public Button getAnotherB(){
return anotherB;
}
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Log.d("me", "Got the custom click!");
}
}
mainlayout.xml from which the default view is made:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/widget474"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<RadioGroup android:id="#+id/widget30" android:orientation="horizontal"
android:layout_x="2dip" android:layout_y="57dip" android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton android:layout_height="wrap_content" android:id="#+id/testrb"
android:textSize="15sp" android:text="Run" android:layout_width="wrap_content"
android:textColor="#ffff99ff"></RadioButton>
</RadioGroup>
<Button android:layout_width="wrap_content" android:text="#string/RUN"
android:id="#+id/goButton" android:layout_height="wrap_content"
android:layout_x="222dip" android:layout_y="110dip"></Button>
</LinearLayout>
mylayout.xml from which the custom view's layout is created:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="#+id/widget0"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<Button android:id="#+id/nextbutton" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:text="work!!!"
>
</Button>
</LinearLayout>
okay, if anybody can explain why any method calls from the button object anotherB (anotherB.setOnClickListener(this) above, but also the simpler anotherB.bringToFront()) cause a force close and a nullpointerexception in logcat with the above implementation I would be most appreciative. thanks!
CCJ
I would declare your button outside of onCreate without the contextHandle parameter... The context will be imbedded in your button upon instantiation (as I understand it).
try:
class YOUR_CLASS {
Button myButton;
onCreate() {
myButton = (Button) findViewById(R.id.WHATEVER_YOU_CALLED_IT_IN_XML);
then you can set an onClickListener or other abilities (you can google that, its easy)
myButton.setOnClickListener(myOnClickListener);
myButton.setText("click me!");
}
}
This sometimes happens to me when the import isn't correct. Sometimes Eclipse will fill in the import as:
import android.R;
of course, this will never find your ID. You should either not have an import, or have something like
import com.myco.mytestapp.R;
If you do that, then the first way of doing it is correct:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
Button b = (Button) findViewById(R.id.mybutton);
}
Okay, thanks to some advice from the android developers google group I think I've found the answer to at least the most pressing concern (the NPE and force close):
I needed to override onFinishInflate in my custom View class; it is at that point that my XML layout child views (like anotherB) are truly instantiated. The class now looks like this
package com.ccg.myactivity;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import android.view.View.OnClickListener;
public class CustomView extends View implements OnClickListener{
private Button anotherB;
private Context contextHandle;
private Activity actHandle;
public CustomView(Context context, Activity act) {
super(context);
contextHandle = context;
actHandle = act;
//anotherB = new Button(contextHandle); //this shouldn't be necessary for
//instantiation from XML widget
initCustomView();
}
public void initCustomView(){
anotherB = (Button) findViewById(R.id.nextbutton);
anotherB.setOnClickListener(this);
}
public Button getAnotherB(){
return anotherB;
}
#Override
public void onFinishInflate(){
anotherB.setOnClickListener(this); //it seems any addressing of child
//views of the layout [the widgets] need to be made after the
//framework calls this method.
}
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Log.d("me", "Got the custom click!");
}
}
Now it pulls up the layout properly and does not throw an NPE. Of course, the onClickListener callback still isn't working right (the message 'Got the custom click!' never appears in logcat), but that's another issue...
thanks all
CCJ
Okay, finally had some time to revisit this issue and I believe I've found the answer:
First, before the xml layout or its components can be addressed they need to be inflated. I knew this, but I wasn't sure when exactly they were inflated. It turns out that setContextView (and probably addContextView) trigger xml inflations. In order to have completely modular activity/view classes, I needed to do something like the following:
Activity Class--
package com.ai.ultimap;
import com.ai.ultimap.views.HomeView;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
public class UltiMapActivity extends Activity {
private View hv;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hv = new HomeView(this);
}
}
Custom View Class-
package com.ai.ultimap.views;
import com.ai.ultimap.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
import android.view.View.OnClickListener;
public class HomeView extends View implements OnClickListener{
private RadioButton twodRB;
private RadioButton threedRB;
private TextView locTV;
private EditText editlocET;
public HomeView(Activity hAct) {
super(hAct);
//THE FOLLOWING LINE INFLATES-- IT (or another function which calls xml inflation)
//MUST COME BEFORE ANY JAVA ADDRESSING OF WIDGETS IN
//THE XML LAYOUT
//Also note that even though you could invoke findViewById from a class extending
//View, in this case you must use hAct.findViewById. I believe this is due to the
//fact that the activity referenced by hAct is the object responsible for inflating
//the xml and thus the widgets need to be instantiated from it.
hAct.setContentView(R.layout.ultimap);
twodRB = (RadioButton) hAct.findViewById(R.id.twodRBV);
threedRB = (RadioButton) hAct.findViewById(R.id.threedRBV);
locTV = (TextView) hAct.findViewById(R.id.locationTV);
editlocET = (EditText) hAct.findViewById(R.id.locationETV);
//After instantiation however they can be freely accessed from java in
//non-activity classes, which is the point; see the next line...
twodRB.setOnClickListener(this);
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
locTV.setText("yo");
}
}
This code works properly to load up the pre-defined xml view ultimap.xml and then address the widgets dynamically from Java (completely outside the activity class), changing the text of the location text view from 'Location' to 'yo' when the twodRB radiobutton is clicked!
Hope this helps some googlers :)
-CCJ