I am new to android development. I have create a view that I want to display in a fragment within an activity.
Here is an excerpt of my view:
public class CalendarView extends View implements View.OnTouchListener {
#Override
protected void onDraw(Canvas canvas) {
displayTitleDate(canvas);
displayNavigation(canvas);
displayDays(canvas);
displayNumbers(canvas);
highlightRect(canvas);
invalidate();
}
}
Here is an excerpt of the fragment:
public class MyCalendarFragment extends Fragment {
private CalendarView calendarView;
public static MyCalendarFragment newInstance()
{
return new MyCalendarFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
calendarView = new CalendarView(getActivity());
return calendarView;
}
}
And finally the excerpt of my activity code:
public class EditPersonActivity extends Activity {
private Fragment calendarFrag;
private Fragment currentFrag;
private FragmentTransaction fragTrans;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_person);
calendarFrag = MyCalendarFragment.newInstance();
fragTrans = getFragmentManager().beginTransaction();
fragTrans.add(R.id.edit_layout, calendarFrag);
fragTrans.commit();
}
}
My view does not show up at all. I guess this is a lifecycle issue. I have also tried to override the onMeasure method in the view but this does not change anything. Besides, it works when the view is embedded in an activity instead of a fragment. Any help is very welcome. Thx in advance.
Finally, I have solved my issue by using a Custom View directly into the xml file describing the fragment.
<com.mycompany.view.CalendarView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
In the java code implementing the view, I had to override one of the constructor this way:
public CalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
//Other code here
}
And finally, I had to override the onMeasure(...) method:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width, height;
Point size = new Point();
WindowManager w = ((Activity)getContext()).getWindowManager();
w.getDefaultDisplay().getSize(size);
width = size.x;
height = (int) (size.x * 1.5);
setMeasuredDimension(width, height);
}
Et voila, it works. Thx for your help.
In Kotlin you can replace doOnPreDraw -> (on) -> doOnLayout.
Related
I am new into Android and I like to code. I just got the requirement to show the screen like this.
User Inerface
My understanding is, there is an Activity which contains ViewPager and ViewPager having multiple Fragments which opened as Popup Dialog.
So I created Activity like this.
public class MainActivity extends AppCompatActivity {
private ViewPager viewPagerMain;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
findViewByIds();
setupImageSlider();
}
private void findViewByIds()
{
viewPagerMain = (ViewPager) findViewById(R.id.viewPagerMain);
}
private void setupImageSlider()
{
viewPagerMain.setAdapter(new MyStatePagerAdapter(getSupportFragmentManager()));
}
static class MyStatePagerAdapter extends FragmentStatePagerAdapter
{
public MyStatePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return OfferFragment.init();
}
#Override
public int getCount() {
return 4;
}
}
}
For now just to show dummy pages I am passing fix size as 4 in getCount()
Here is my OfferFragment class
public class OfferFragment extends Fragment {
private View view;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_offer, container, false);
return view;
}
public static OfferFragment init() {
OfferFragment offersFragment = new OfferFragment();
Bundle args = new Bundle();
offersFragment.setArguments(args);
return offersFragment;
}
}
Here are my some thoughts why I created init() method in Fragment class. As I don't know how many pages will have to be shown, in that case creating Fragment object may cause exception.
I know there is DialogFragment class which shows Fragment as Dialog. but in my case I am calling init() from Activity class and inside init() creating object of Fragment class.
So if I extend DialogFragment then, I have to call setShowDialog() method inside onCreate() of Fragment and it give me below error.
Caused by: java.lang.IllegalStateException: DialogFragment can not be attached to a container view
Kindly let me know how to Create Fragment in ViewPager and show as Popup
Thanks in Advance
This type of question is asked many times in SO, I have tried all of them, but couldn't achieve what I actually want. In my app I have Fragment, inside that fragment I have a recycler view. recyclerview would be manipulated by the data which I have got from API.
What I want that when rotation is changed, the app doesn't call the API again. To achieve this I know I have to put the data in onSaveInstanceState, If I want to save a complex object then the class must be implements Parcelable. I have done everything that is recommended, but couldn't achieve what I want.
I am sharing my code here, I have created a simplified example.
Code for SimpleFragment.java
public class SimpleFragment extends Fragment {
RecyclerView recyclerView;
ArrayList<TestModel> testModelList = new ArrayList<TestModel>() ;
SimpleAdapter simpleAdapter;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.simple_fragment, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(mLayoutManager);
simpleAdapter = new SimpleAdapter(getActivity());
recyclerView.setAdapter(simpleAdapter);
if(savedInstanceState != null){
//if this fragment starts after a rotation or configuration change, load the existing model from a parcelable
testModelList = savedInstanceState.getParcelableArrayList("key");
// I have done necessary debug , after rotation , it comes here ,
// but suddenly savedInstanceState becomes null and getTestModelList() get called
}else {
//if this fragment starts for the first time, load the list of model
getTestModelList();
}
simpleAdapter.setModel(testModelList);
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("key",testModelList);
}
public void getTestModelList(){
for (int i=0;i<10;i++){
testModelList.add(new TestModel(i,"test"+i));
}
Toast.makeText(getActivity(),"Called",Toast.LENGTH_SHORT).show();
}
}
layout file for SimpleFragment: simple_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">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:background="#android:color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Here is code for my data model TestModel.java
public class TestModel implements Parcelable {
String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public TestModel(int number, String text) {
this.text = text;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.text);
}
protected TestModel(Parcel in) {
this.text = in.readString();
}
public static final Parcelable.Creator<TestModel> CREATOR
= new Parcelable.Creator<TestModel>() {
public TestModel createFromParcel(Parcel in) {
return new TestModel(in);
}
public TestModel[] newArray(int size) {
return new TestModel[size];
}
};
}
My adapter class: SimpleAdapter.java
public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.ViewHolder> {
Context context;
ArrayList<TestModel> testModels;
public SimpleAdapter(Context context) {
this.context = context;
}
public void setModel(ArrayList<TestModel> model){
this.testModels = model;
notifyDataSetChanged();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_row, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.list_item.setText(testModels.get(position).getText());
}
#Override
public int getItemCount() {
return testModels.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView list_item;
public ViewHolder(View itemView) {
super(itemView);
list_item = (TextView) itemView.findViewById(R.id.list_item);
}
}
}
Code for single row layout for adapter single_row.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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:id="#+id/list_item"
android:textColor="#android:color/white"/>
</LinearLayout>
These are what I have done, and if change the orientation getTestModelList() method in my SimpleFragment is called again, which I want to avoid; in real app scenario this method will do some API call. I want to save the state of ArrayList<TestModel> testModelList.
you need to save state of activity and check it before configuration
follow this code
public class MainActivity extends AppCompatActivity{
private HomeFragment homeFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
homeFragment = new HomeFragment();
((fm.beginTransaction()).replace(R.id.mainframe, homeFragment)).commit();
}
}}}
You can save Fragment instance like this in Fragment onCreate method for orientation change to save instance :
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set Instance true
this.setRetainInstance(true);
}
Android recreates your activity whenever the orientation changes. The reason behind this is you may need to change your layout but you can still achieve that with onConfigurationChanged() of the Activity or the Fragment I suggest you to add
android:configChanges="orientation|keyboardHidden|screenSize"
in your manifest file (refer to this and this). This will stop recreation of Activity on Confifuration changes.
I have created an external class, NotesView, which extends View for implementation in my MainActivity.
This View requires information passed from the MainActivity, so its constructor takes an ArrayList of Note objects.
public class NotesView extends View {
private ArrayList<Note> notes = new ArrayList<>();
public NotesView(Context context, ArrayList<Note> notes) {
super(context);
this.notes = notes;
}
In my MainActivity, I used the following code to display this view: (Trying to add a CustomView in the Design tab of the layout does not work as I cannot supply the ArrayList parameter)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
notesView = new NotesView(this, noteList);
setContentView(notesView);
}
Unfortunately, I am now not able to add any objects at all through the Design view of the layout, I assume this is because I have used setContentView. I do not wish to add all my components programmatically, is there a way around this?
Calling setContentView replaces the whole view for your layout. That means if you call setContentView twice, whatever was added to the screen from the first call is overridden and no longer accessible.
There are multiple answers to your question, here is a pragmatic one:
What is inside R.layout.activity_main? Let's assume there is a FrameLayout / LinearLayout / RelativeLayout with id root
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewGroup rootView = (ViewGroup) findViewById(R.id.root);
notesView = new NotesView(this, noteList);
rootView.addView(notesView);
}
Another choice, you could also take your custom view to have a setter if you wish:
public class NotesView extends View {
private final List<Note> notes = new ArrayList<>();
public NotesView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void replaceNotes(List<Note> notes) {
this.notes.clear();
this.notes.addAll(notes);
}
Then you can add this view in your XML file (R.layout.activity_main) and call the setter method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NotesView notesView = (NotesView) findViewById(R.id.notes);
notesView.replaceNotes(noteList);
}
You can add a setter function to your NotesView class:
public class NotesView extends View {
private ArrayList<Note> notes;
public NotesView(Context context) {
super(context);
}
public void setNotes(ArrayList<Note> notes) {
this.notes = notes;
}
}
And then set it in the main activity:
NotesView notesView = (NotesView) findViewById(R.id.yourNotesView);
notesView.setNotes(noteList);
By the way I recommend Butterknife to cast views in your layout without the verbose findViewByIds, declarations, onXListeners, etc.
I have created a custom view that is a sub-class of RelativeLayout. The view is part of a Fragment set to retain its instance state. I want to save state information (only one boolean) when the orientation of the device changes, so I implemented onSaveInstanceState and onRestoreInstanceState with my custom extension of BaseSavedState. Neither the Fragment, nor the Activity have implemented anything that deals with saving/restoring the instance state. When I change the device orientation, it saves the state, recreates the view with the saved state, but then recreates it again without calling onRestoreInstanceState.
The sequence of events goes like this, showingProgress is the boolean I want to save:
View#1 (onSaveInstanceState) showingProgress=true
View#2 (ctor) showingProgress=false
View#2 (onFinishInflate)
View#2 (onRestoreInstanceState) showingProgress=true
View#2 (showProgress)
View#2 (onSaveInstanceState) showingProgress=true
View#3 (ctor) showingProgress=false
View#3 (onFinishInflate)
After that, the view is reconstructed, but since onRestoreInstanceState is never called for View #3, it is always in its initial state.
Why does it recreate the view twice?
How can I prevent the second view from recreating again, or pass the saved state along to the third view respectively?
EDIT:
Relevant parts from the Activity
private Fragment currentFragment = null;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.single_fragment);
getSupportActionBar().setDisplayShowTitleEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().show();
showLoginFragment();
}
public void showLoginFragment()
{
final FragmentTransaction t = this.getSupportFragmentManager().beginTransaction();
currentFragment = MyFragment.newInstance();
t.replace(R.id.layoutRoot, currentFragment);
t.commit();
}
The layout single_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/layoutRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
Class MyFragment
public class MyFragment extends RoboFragment
{
// I'm using roboguice
#InjectView(R.id.myButton) private ProgressButton myButton;
public static MyFragment newInstance()
{
Bundle arguments = new Bundle();
// no arguments for now, this comes later
MyFragment fragment = new MyFragment();
fragment.setArguments(arguments);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
setRetainInstance(true);
View result = inflater.inflate(R.layout.my_fragment, container, false);
return result;
}
}
The layout my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.my.package.ProgressButton
android:id="#+id/myButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/button_caption" />
</LinearLayout>
And finally the code for the ProgressButton
/**
* A Button with an integrated progress spinner. The button can show the progress spinner while some background process
* is running to indicate it can't be clicked again.
*/
public class ProgressButton extends RelativeLayout
{
/** The view holding the button text */
private TextView textView = null;
/** The progress spinner */
private ProgressBar progressSpinner = null;
private boolean showingProgress = false;
public ProgressButton(Context context)
{
super(context);
textView = new TextView(context);
progressSpinner = new ProgressBar(context);
initView();
}
public ProgressButton(Context context, AttributeSet attrs)
{
super(context, attrs);
textView = new TextView(context, attrs);
progressSpinner = new ProgressBar(context, attrs);
initView();
}
public ProgressButton(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
textView = new TextView(context, attrs, defStyle);
progressSpinner = new ProgressBar(context, attrs, defStyle);
initView();
}
/**
* Initializes the view with all its properties.
*/
#SuppressWarnings("deprecation")
private void initView()
{
// remove the background attributes from progressbar and textview, because they should be transparent
if (Build.VERSION.SDK_INT >= 16)
{
textView.setBackground(null);
progressSpinner.setBackground(null);
}
else
{
textView.setBackgroundDrawable(null);
progressSpinner.setBackgroundDrawable(null);
}
}
#Override
protected void onFinishInflate()
{
super.onFinishInflate();
if (!isInEditMode())
{
progressSpinner.setVisibility(View.INVISIBLE);
}
RelativeLayout layout = new RelativeLayout(getContext());
layout.setId(R.id.progressButtonContainer);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
this.addView(layout, params);
params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_LEFT);
params.leftMargin = 10;
progressSpinner.setClickable(false);
progressSpinner.setId(R.id.progressButtonProgress);
layout.addView(progressSpinner, params);
params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
textView.setClickable(false);
textView.setId(R.id.progressButtonText);
layout.addView(textView, params);
}
/**
* Disables the button and shows the progress spinner.
*/
public void showProgress()
{
this.setEnabled(false);
progressSpinner.setVisibility(View.VISIBLE);
showingProgress = true;
}
/**
* Enables the button and hides the progress spinner.
*/
public void hideProgress()
{
this.setEnabled(true);
progressSpinner.setVisibility(View.INVISIBLE);
showingProgress = false;
}
#Override
public Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
return new SavedState(superState, showingProgress);
}
#Override
public void onRestoreInstanceState(Parcelable state)
{
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
showingProgress = savedState.showProgress;
if(showingProgress)
{
showProgress();
}
else
{
hideProgress();
}
}
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
protected void dispatchSaveInstanceState(SparseArray container)
{
super.dispatchFreezeSelfOnly(container);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
protected void dispatchRestoreInstanceState(SparseArray container)
{
super.dispatchThawSelfOnly(container);
}
static class SavedState extends BaseSavedState
{
boolean showProgress = false;
SavedState(Parcelable superState, boolean showProgress)
{
super(superState);
this.showProgress = showProgress;
}
private SavedState(Parcel in)
{
super(in);
this.showProgress = in.readByte() == 0 ? false : true;
}
#Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeByte((byte) (showProgress ? 1 : 0));
}
// required field that makes Parcelables from a Parcel
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
{
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
}
After the helpful comment by Daniel Bo, I got the solution pretty easily. The fragment is added in the activity regardless of there being a saved state or not. This causes the addition of View #3 in its initial state. Wrapping showLoginFragment(); in the Activities onCreate method in the following if loop solves the problem.
if(savedInstanceState == null)
{
showLoginFragment();
}
I have a custom view extending RelativeLayout (could be any other viewgroup). In this view I have several framelayouts added with addView(). No XML exists for this view! The view constructer adds everything from code.
I want to use this custom view as any other content view for an activity.
I have this fragment activity:
MyView myview;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myview = new MyView(this);
myview.getSomeContainedFrameLayoutOfView().setId(12345);
setContentView(myview);
}
Then I do this in onResume
#Override
protected void onResume() {
super.onResume();
View u = findViewById(12345); // test, returns null
u = myview.findViewById(12345); // test, also null
SomeFragment f = new SomeFragment();
FragmentManager m = getSupportFragmentManager();
FragmentTransaction ft = m.beginTransaction();
ft.replace(12345, f); // ofcourse error (no view found for id)
ft.commit();
}
Why is findViewById null? If I change setContentView() line to
setContentView(R.layout.myframelayouts);
Everything works. So somehow I suspect
setId(12345)
does something different from:
android:id="#+id/someid"
What am I missing?
Is the view returned by getSomeContainedFrameLayoutOfView() actually contained in the RelativeLayout? That is, somewhere in MyView have you called AddView(theTargetView)?
If the target view is simply a member of MyView, then the RelativeLayout, which implements findViewById unless you override it, doesn't know about the target view.
I have something like this:
public class MojLayout extends LinearLayout {
private FrameLayout lijeviFrame;
private FrameLayout glavniFrame;
private int layoutWidth;
private int panel;
private boolean glavniFrameFullScreen = false;
public MojLayout(final Context context, final float leftWeight) {
super(context);
lijeviFrame = new FrameLayout(context);
glavniFrame = new FrameLayout(context);
post(new Runnable() {
#Override
public void run() {
layoutWidth = getWidth();
panel = (int) (layoutWidth / leftWeight);
init(context);
}
});
}
private void init(Context context) {
lijeviFrame.setId(R.id.lijeviFrame); //defined in /res/values/ids.xml
glavniFrame.setId(R.id.glavniFrame);
LinearLayout container = new LinearLayout(context);
container.setOrientation(HORIZONTAL);
....
container.addView(lijeviFrame);
container.addView(glavniFrame);
addView(container);
}
In onCreate() method I tried myView.findViewbyId(R.id.lijeviFrame) -> null, without myView, same.
I don't get it