In my PreferenceFragment there is a ListPreference which is programmatically populated at onCreate(). Thus, there is a little lag at the fragment start. In order to avoid it I would like to populate the ListPreference only when the preference has been pressed, and put a indeterminate ProgressBar while the content is loading.
How could I implement this? Do I really need to rewrite the adapter? How can I get the view for the ListPreference to pass to View.OnClickListener?
I hope this is not a trivial question, I have googled for a while but I didn't understand how should I actually implement the whole thing.
Thank you in advance.
I've created a custom PreferenceFragment for this.
You can extend this one instead of a PreferenceFragment and delay your call to addPreferencesFromResource till after all loading is done, which will then hide the ProgressBar and display your content.
public class ProgressBarPreferenceFragment extends PreferenceFragment {
private FrameLayout progress;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
showLoading((ViewGroup) view);
return view;
}
#Override
public void addPreferencesFromIntent(Intent intent) {
super.addPreferencesFromIntent(intent);
hideLoading();
}
#Override
public void addPreferencesFromResource(int preferencesResId) {
super.addPreferencesFromResource(preferencesResId);
hideLoading();
}
private void hideLoading() {
ViewGroup root = (ViewGroup) getView();
if ((root != null) && (progress != null)) {
root.removeView(progress);
}
}
private void showLoading(ViewGroup root) {
progress = new FrameLayout(root.getContext());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER;
progress.addView(new ProgressBar(root.getContext()), lp);
LayoutParams lp2 = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
root.addView(progress, lp2);
}
}
You could use an AsyncTask to populate your ListPreference asynchronously and then remove the indeterminate progress bar upon completion.
First, extend AsyncTask like so:
private class PopulateListPreferenceTask extends AsyncTask<CharSequence, Void, Void> {
protected Long doInBackground(CharSequence... entries) {
// Code to populate ListPreference in here
}
protected void onPostExecute() {
// Add code here if necessary to retrieve the progress bar view, i.e. findViewById
myIntederminateProgressBar.setVisibility(View.GONE);
}
}
Have your indeterminate progress bar shown initially. Then, in your fragment's onActivityCreated method, call:
new PopulateListPreferenceTask().execute(/*CharSequence entries go here*/);
A couple notes:
I am assuming that you would want to provide Charsequences as
input, but you can use any other type.
If you need the AsyncTask to receive the Activity context you can
overload the constructor to receive it as a parameter and then
store it in a field.
Related
I have a main Activity and several Fragments. On each fragment I have several TextView elements. I want to change font size of TextViews on the current displayed Fragment from Main Activity. Therefore I want to hold list of TextView elements on the currentFragment using a Listener.
But I don't know how to implement such Listener?
Is the listener right way to do that?
If there is a another way to achieve this, I wanted to know. Any answers welcome. Thanks.
If I got your question right, you could just access the current fragment's TextView object from the Main Activity and use the setTextSize() method.
You declare a list of TextViews and provide a method to add the textviews to the list.
And you also provide a method to signal the activation state. This method will go through the list of textviews calling the changes you need one by one.
From your main activity you will call fragment.changeListeningTextViews();
This goes in the Fragment:
private ArrayList<TextView> listeningTextViews;
public void addListeningTextview(TextVew tv){
//Here check if the text view is already added not to add it twice.
listeningTextViews.add(tv);
}
public void changeListeningTextViews(){
for(TextView tv : listeningTextViews){
tv.setFont(...);
//What you want called on each TextVeiw
}
}
In onCreateView you add the text views to the list:
TextView textView1 = (TextView) rootView.findViewById(R.id.aview);
addListeningTestview(textView1);
TextView textView2 = (TextView) rootView.findViewById(R.id.anotherview);
addListeningTestview(textView2);
...
Note: If it were the other way round when you need the Main Activity to listen on events coming from the fragment the solution would be different and the it would involve a custom Listener interface to be implemented by the Main Activity.
I had implemented as below. It works well fine but I don't know how clear solution it is.
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
//
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action buttons
switch(item.getItemId()) {
case R.id.smallFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize1);
return true;
case R.id.mediumFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize2);
return true;
case R.id.largeFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize3);
return true;
case R.id.extraLargeFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize4);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
public class FontSizeHelper {
public static List<View> viewElements = new ArrayList<>();
public static void initFontElements(){
viewElements = new ArrayList<>();
}
public static void addFontEelements(View view){
viewElements.add(view);
}
public static void updateFontSize(int fontSize){
for(View v : viewElements){
if(v instanceof TextView){
((TextView) v).setTextSize(fontSize);
}
}
}
}
public class FragmentA extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.a_fragment, container, false);
//initialize empty list for View objects.
FontSizeHelper.initFontElements();
//add View object/TextViews into list
FontSizeHelper.addViewEelements(textView1);
FontSizeHelper.addViewEelements(textView2);
FontSizeHelper.addViewEelements(textView3);
//..etc
}}
What i pretend is to display an indeterminate progress bar (for the moment is only an dialog for test purposes) while my second fragment is getting data and constructing the view without freezing the UI.
What i've tried:
AsyncTask
Loader
Loader with AsyncTaskLoader
Adapter (not tried but maybe its an option but i don't know how to
use an adapter with a custom layout)
With the asynctask approach almost everything is right, but onPostExecute I've to update the fragment to do this i created an interface and in the main activity i remove the view and add the new one, but with this i created another problem my back stack is messed up so i'am out of options.
A diagram to help understand how it works:
My fragment 2:
public class FragTopics extends Fragment {
Object course;
ManSession session;
String courseId;
Long topicId;
String courseName;
private LinearLayout mainLayout;
private ScrollView contentScrollable;
private LinearLayout contentsLayout;
private View myView;
public FragTopics() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
session = new ManSession(getActivity().getApplicationContext());
courseId = getArguments().getString("courseId");
topicId = Long.parseLong(getArguments().getString("topicId"));
courseName = getArguments().getString("courseName");
new HeavyWork().execute();
// Create empty view because i need to return something
myView = new View(getActivity());
return myView;
}
private class HeavyWork extends AsyncTask<Void, Void, Void> {
private ProgressDialog dialog;
final FragmentUpdater activity = (FragmentUpdater) getActivity();
// Do the long-running work in here
#Override
protected Void doInBackground(Void... params) {
MoodleCourseContent[] courseTopics = new ManContents(getActivity()
.getApplicationContext()).getContent(courseId);
MoodleCourseContent singleTopic = new ManContents(getActivity()
.getApplicationContext()).getTopic(topicId, courseTopics);
// This createTopics call another methods from the fragment class to
// get the data and create views
myView = createTopics(singleTopic, courseName, courseId, topicId);
return null;
}
protected void onPreExecute() {
dialog = new ProgressDialog(getActivity());
dialog.show();
}
// This is called when doInBackground() is finished
#Override
protected void onPostExecute(Void ignore) {
activity.updater(myView);
dialog.dismiss();
}
}
My interface:
public interface FragmentUpdater {
public void updater(View param);
}
My main activity where the interface is implemented:
#Override
public void updater(View param) {
ViewGroup vg = (ViewGroup) findViewById (R.id.main_content);
vg.removeAllViews();
vg.addView(param);
}
My layouts are created programmatic and this is structure:
->LinearLayout
->TextView
->ScrollView
->LinearLayout
My main question is how to display a progress bar while fragment 2 is initialized without freezing the UI and if my above approach is correct, how to update the view in the fragment after the heavyWork is done in other words after asyncTask is done doing the background work
After remove all view and add the new one onBackpressed will overlap my view with the previous fragments views
Your fragment is headless. So don't implement the onCreateView method. Move that code to onCreate. Then in the async task, use onPostExecute to signal the parent activity with updated information for it to display. The parent activity of this fragment can then decide how the UI should react to this information. Make sure you avoid communicating with the parent activity anywhere other than the onPostExecute (or onPublishProgress, if you're updating) method, since a) the activity can be null for a number of reasons, including a device rotation and b) these methods are gauranteed to run on the UI thread
I have a Fragment that loads data from web
So I'd like to make a RelativeLayout to show to user that data is still loading
When finished I want to make my RelativeLayout disappear
But here's the problem..
On my Xml of Fragment I put my RelativeLayout with my loading bar visible..
So my fragments goes throught this step:
1)onCreate() {inside this I have Asynctask.execute()
2)onCreateView() {And here I can manage my RelativeLayout with loadingbar through Inflater and View}
3)Asyntask.onPostExecute() {And here I want to make disappear my relativelayout..}
BUT in Asynctask there's no way to access to my relativelayout, and of course if I try app crashes because of NullPointerException [Obvious]
How can I manage this problem?
It seems he did the following:
private RelativeLayout mRelativeLayout;
mRelativeLayout = findViewById(R.Layout.relativeLayoutId);
And use it later on in the class like
mRelativeLayout.doTheMagic();
Modify the constructor of your AsyncTask to accept a reference to the View, then you can modify it during the onPreExecute() and onPostExecute(Result) methods. Why aren't you just using a ProgressDialog instead? Much easier.
That's how I solved:
public class HomeFragment extends Fragment {
RelativeLayout mLoading_Bar;
public void onCreate(Bundle savedInstanceState) {
Asynctask.execute();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View cView = inflater.inflate(R.layout.fragment_home, container, false);
mLoading_Bar = (RelativeLayout) cView.findViewById(R.id.fragment_home_loadingdata_layout);
return cView;
}
class ASynctask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... args) {
//do something
}
protected void onPostExecute(Void string) {
mLoading_Bar.setVisibility(View.GONE);
}
}
}
And that's all folks
Sometimes I need to do some operations(e.g. changing layout) when the activity is just showing. What I do now is using post():
public class MyActivity extends Activity {
#Override
public void onCreate() {
...
container.post(new Runnable(){
resize(container);
});
}
}
Is there any lifecycle method like onCreate can be used to simplify the code, that I don't need to call post?
#Override
public void onX() {
resize(container);
}
I think you mean do something after the UI is displayed.
Using a global layout listener has always worked well for me. It has the advantage of being able to remeasure things if the layout is changed, e.g. if something is set to View.GONE or child views are added/removed.
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// inflate your main layout here (use RelativeLayout or whatever your root ViewGroup type is
LinearLayout mainLayout = (LinearLayout ) this.getLayoutInflater().inflate(R.layout.main, null);
// set a global layout listener which will be called when the layout pass is completed and the view is drawn
mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
// at this point, the UI is fully displayed
}
}
);
setContentView(mainLayout);
http://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html
MainActivity.java:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setTitle(R.string.app_name);
setContentView(new SampleView(this));
}
}
SampleView.java:
public class SampleView extends View {
#Override
protected void onDraw(Canvas canvas) {
if (certaincondition = true) {
//add elements to canvas etc
} else {
//How do I do the below? The layout is defined in xml.
//I do not want to use Intent. Please help me
//create a layout from resource R.layout.idAbout and transfer control.
}
}
}
Use a layout inflater:
View newRootViewElement;
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
newRootViewElement= li.inflate(R.layout.idAbout, null);
You can inflate a layout using
View.inflate(getContext(), R.layout.idAbout, viewParent);
where viewParent is a ViewParent that will be the parent of the inflated view (and can be null).
But what are you trying to do? It's more than a little odd to start a new activity or to modify the view hierarchy from within onDraw(). You might want to post a runnable to a Handler that will do what you want on the next cycle of the event loop. To start a new activity (such as displaying “About” info for the app) you should take a look at the Intent class.