I am experimenting with some Animations with Android. Basically I have this Activity that displays data from an API. Now when the user wishes to refresh that data, the Front card so to speak of which contains the data, flips to the Back card which is a loading animation. The process of loading new data kicks off and when it finishes the method that flips the so to speak of cards is called so the data can be revealed.
With my intentions in place I have everything explained above 99% complete. When I have the data from the API back in the UI thread controlling the cards, I am ready to update those interface objects that are part of the front card.
But setting the text of a TextView no longer works, and I'll explain that. When the front card gets switched out, the back card comes to the foreground, but when it comes time for the front card to come back with new data, it comes back with its default XML values. I can set TextViews all day of this front card but it return blank.
Code is below, please keep in mind I like to reverse engineer things like this. So some code I do not understand, it the sample from the Google Android development guide.
Here I set up some kind of fragment manager in the onCreate method of the Activity.
mShowingBack is a boolean for if the back card is the card in the foreground.
if (savedInstanceState == null) {
// If there is no saved instance state, add a fragment representing the
// front of the card to this activity. If there is saved instance state,
// this fragment will have already been added to the activity.
getFragmentManager()
.beginTransaction()
.add(R.id.container, new CardFrontFragment())
.commit();
} else {
mShowingBack = (getFragmentManager().getBackStackEntryCount() > 0);
}
// Monitor back stack changes to ensure the action bar shows the appropriate
// button (either "photo" or "info").
getFragmentManager().addOnBackStackChangedListener(this);
Even if I do not update the textviews with the new data, the front card is still bare and empty. Below is the method that flips the the two cards:
public void onClickFlipCard(View view) {
if (mShowingBack) {
getFragmentManager().popBackStack();
return;
}
// Flip to the back.
mShowingBack = true;
// Create and commit a new fragment transaction that adds the fragment for the back of
// the card, uses custom animations, and is part of the fragment manager's back stack.
getFragmentManager()
.beginTransaction()
// Replace the default fragment animations with animator resources representing
// rotations when switching to the back of the card, as well as animator
// resources representing rotations when flipping back to the front (e.g. when
// the system Back button is pressed).
.setCustomAnimations(
R.animator.card_flip_right_in, R.animator.card_flip_right_out,
R.animator.card_flip_left_in, R.animator.card_flip_left_out)
// Replace any fragments currently in the container view with a fragment
// representing the next page (indicated by the just-incremented currentPage
// variable).
.replace(R.id.container, new CardBackFragment())
// Add this transaction to the back stack, allowing users to press Back
// to get to the front of the card.
.addToBackStack(null)
// Commit the transaction.
.commit();
}
When that method is called, even though it is not included the method call for retrieving the new data kicks off. I am not for sure if like creates a new instance of the card layout so when I try to modify it, I am not actually in touch with it or something?
When I get the data for example all I do is use the textview object used before the data load to set the new data:
textview_example.setText("Example, but nothing appears...")
Even further, when I call the textviews getText method, it returns text but none is shown...Any help would be great. If it matters here are the two fragment classes that represent the two cards,(The front where data is displayed and the back where a loading animation is shown).
Front Card Fragment:
public class CardFrontFragment extends Fragment {
public CardFrontFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_card_front, container, false);
}
}
Back Card Fragment
public class CardBackFragment extends Fragment {
public CardBackFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_card_back, container, false);
}
}
Here is also a link to the Android guide that I follow...
Animation works but afterward as I said I can not edit any of the properties of interface objects. Thanks!
Link: http://developer.android.com/training/animation/cardflip.html
Related
Please give me a hand with an issue I am having with a ListView and its related data in my android development project.
I have an activity called OrderForm that gets started by an Intent from the activity UserProfile as such:
In UserProfile
Intent intent = new Intent(this, OrderForm.class);
startActivity(intent);
Then in OrderForm there is an EditText and an add button to add String items to an ArrayList, and the UI gets populated accordingly.
When I click the back button (back to UserProfile) and go via the Intent to OrderForm again, the UI does not show the list items, why is that?
I realize I can use Room for persistence and even SharedPreferences, but
I wanted to see if there is cleaner, more efficient method, otherwise the less code the better.
Also, maybe I'm not understanding them correctly, but I tried onSaveInstanceState and onRestoreInstanceState and they don't work for me.
Thanks in advance.
Here is part of the code from OrderForm
public class OrderForm extends AppCompatActivity {
ArrayList<String> list;
ListView itemList;
ArrayAdapter<String> arrayAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order_form);
itemList = findViewById(R.id.itemList);
itemText = findViewById(R.id.item);
linearLayout = findViewById(R.id.no_items_container);
orderContainer = findViewById(R.id.orderContainer);
list = new ArrayList<>();
arrayAdapter = new ArrayAdapter<String>(this,
R.layout.list_item,R.id.rowItem, list)
{
#Override
public View getView(int position,
View convertView,
ViewGroup parent) {
// some custom stuff here
}
}
public void addItem(View view)
{
String item = itemText.getText().toString().trim().toLowerCase();
if(!item.isEmpty() && list.indexOf(item) == -1) {
arrayAdapter.add(item);
}
}
You need to understand how Activity lifecycles work.
https://developer.android.com/guide/components/activities/activity-lifecycle.html
Your issue is that when pressing the back button, your OrderForm Activity is destroyed and effectively your arraylist/list view is destroyed. To avoid this problem, you'll have to store the values somewhere for example SharedPreferences, create a text file holding your strings and store it, or return the arraylist back to the UserProfile class where you'll store/handle them (to do that use startActivityForResult() instead of startActivity())
When I click the back button (back to UserProfile) and go via the Intent to OrderForm again, the UI does not show the list items, why is that?
In your onCreate() method, you have this:
list = new ArrayList<>();
arrayAdapter = new ArrayAdapter<String>(..., list)...
Unless you persist your data in some way, list is always going to be empty when your activity starts up.
I realize I can use Room for persistence and even SharedPreferences, but I wanted to see if there is cleaner, more efficient method, otherwise the less code the better.
Exactly what you need to store will help define the best way to store it. For a simple list of strings, probably SharedPreferences is the simplest solution.
Also, maybe I'm not understanding them correctly, but I tried onSaveInstanceState and onRestoreInstanceState and they don't work for me.
These methods are used to store data when an activity is destroyed and then recreated, which commonly happens when the user rotates the device (but can also happen for various other reasons). When you exit your activity (by pressing back to UserProfile), these methods aren't triggered.
I am trying to refresh the content in text view upon call back. In my onCreateView() when I first load the fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.activity_expense,container,false);
total = 0;
// some logic to get the total
txtTotal.setText(String.valueOf(total));
return v;
}
From first fragment I set the call back when button on click go to second fragment:
// set call back
addFragment.setTransactionListCallBack(ExpenseActivity.this);
Then when finish updating and retrieve in second fragment using async task, I pass back the updated results:
if(mcallback != null){
mcallback.callback(GetMonthlyTransactionAsyncTask.allTransaction);
}
Then I perform some insert into database, but before I return I am pulling the data again so I will pass back the updated one. In my callback in first fragment:
#Override
public void callback(ArrayList<Transaction> list)
{
// logic to sum up the total
Log.d("UPDATED TOTAL", String.valueOf(total));
txtTotal.setText(String.valueOf(total));
}
The call back is working perfectly as I did printed out the log to check the before update (inside onCreateView) and after update (inside callback), the value printed out is correct. So I guess there is not a need to show the code.
The problem now is inside my callback in first fragment, the updated value I printed out in Log is correct which is the updated one, but not the content displayed in the text view. The text view is still stuck with the old value.
Any ideas how to refresh the textview content upon call back?
invalidate() must be called from a UI thread. To call from a non-UI thread, call postInvalidate()
#Override
public void callback(ArrayList<Transaction> list)
{
// logic to sum up the total
Log.d("UPDATED TOTAL", String.valueOf(total));
txtTotal.setText(String.valueOf(total));
txtTotal.postInvalidate();
}
Also, check if there is a textview with the same id.
You may be trying to set the textview on a different thread if asynctask is involved, try setting the text view on the UI thread:
runOnUiThread(new Runnable() {
#Override
public void run() {
txtTotal.setText(String.valueOf(total));
}
});
Edit: also try txtTotal.invalidate() after you set text.
I have a frame layout in an activity to which i want to display different fragments inside. I have a sliding drawer with 3 options, each of which lead to a fragment being loaded inside the frame layout. Currently i use the following to accomplish this:
Fragment nextFragment = determineFragmentToSwitchTo(nextFragmentTag);
transaction.replace(R.id.fragment_container, nextFragment);
The first method determines what fragment i need by evaluating the nextFragmentTag string and loading a new fragment like so:
if (fragmentTag.equals(Constants.STUDENTPAGE))
nextFragment = new StudentFragment();
else if (fragmentTag.equals(Constants.TEACHERPAGE))
nextFragment = new TeacherFragment();
else if (fragmentTag.equals(Constants.PARENTPAGE))
nextFragment = new ParentFragment();
Clearly this approach is creating a new fragment each time and running through the whole fragment lifecycle without saving state. So if i am on the student page and scrolling through the student list and i switch to the parent page, when i go back to the student page, it reloads the entire list (i am fetching it from a server) and looses my place in it. How can i get it to persist state and sort of cache that fragment in the manager (if that makes sense)?
You could use the FragmentTransaction's hide(Fragment) and show(Fragment) methods, e.g.:
// In the parent Activity
StudentFragment studentFragment;
TeacherFragment teacherFragment;
ParentFragment parentFragment;
Fragment fragmentOnDisplay;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Initialize fragmentManager, fragmentTransaction, etc.
studentFragment = (StudentFragment) fragmentManager.findFragmentByTag(Constants.STUDENTPAGE);
if (studentFragment == null) {
studentFragment = new StudentFragment ();
fragmentTransaction.add(R.id.your_frame_layout, studentFragment, Constants.STUDENTPAGE);
}
// repeat the same procedure for the other two fragments
// Suppose you want to begin with the teacherFragment on
// display - in that case hide the studentFragment and
// the parentFragment:
fragmentTransaction.hide(studentFragment);
fragmentTransaction.hide(parentFragment);
fragmentOnDisplay = teacherFragment;
fragmentTransaction.commit();
}
Now whenever you need to switch your fragments, simply hide the fragment on display, and show the fragment you need, e.g.:
...
fragmentTransaction.hide(fragmentOnDisplay);
fragmentTransaction.show(parentFragment);
fragmentOnDisplay = parentFragment;
fragmentTransaction.commit();
I've been looking for a way to have the blank detail side of my fragment layout host a welcome screen (or something - login perhaps) on start up. Afterwards, when a user presses one of the left side menu items, I'd like to eliminate the fragment for the remainder of the program run. I don't want to add it to the backstack, as that messes up my configuration changes. I've considered using shared prefs to host a boolean about whether the fragment has been displayed. The only concern with this method is where to safely reset the boolean value for the next run of the app. I'm of the impression that there's no gaurantee that the onStop, onDetach etc. will definitely get called upon closing of the app, so if the app got closed in the wrong state, it would be rendered useless ( the first fragment wouldn't display - crash )
Anyone have any ideas on how I could implement a filler for the right side of the app upon startup?
I've been trying to add something to the onCreate of my main activity thus far with no success.
Thanks in advance.
Ken
If your fragment can be part of its own Activity, you can use the android:noHistory="true" attribute to keep the Activity off of the backstack. If your user tries to navigate backwards, it'll hit the bottom of the backstack twice before exiting your application.
If you can't split your fragment into its own activity, noHistory may not work -- I can't say as I haven't tried it myself.
I was able to come up with a solution to creating a welcome or login screen which will display both fragments and activities from the main activity. Seems to be working fine as tested.
private boolean welcomeShown;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_list);
if (findViewById(R.id.item_detail_container) != null) {
mTwoPane = true;
((MainFragment) getSupportFragmentManager().findFragmentById(
R.id.item_list)).setActivateOnItemClick(true);
}
if (savedInstanceState != null){
welcomeShown = savedInstanceState.getBoolean("displayed");
}
if(!welcomeShown){
if (mTwoPane){
WelcomeFragment welcomeFragment = new WelcomeFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.item_detail_container, welcomeFragment)
.commit();
}
else{
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
startActivity(welcomeIntent);
welcomeShown = true;
}
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("displayed", true);
}
I've currently got an activity that creates a view. this view uses other classes (such as one to create a random sequence of integers). I need to run a method (which will display the sequence using bitmaps) once the view is created. So once the user clicks "Start Game" this sequence will be displayed.
I've tried calling the method after setting the content view inside the onCreate method by the sequence is not generated (all 0's) correctly. I've tries this also with onStart and onFinishInflate inside the myView class.
Is there a way i can run this method after everything is inflated and initialized? So after the user clicks "Start Game" and the view is changed, the method needs to run.
Thanks for looking.
Edit: A failed attempt.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.gameView = new GameView(getApplicationContext(), getCellSource(getApplicationContext()));
setContentView(this.gameView);
// this.gameView.displaySequence(this.gameView.gameEngine.getGenSequence()); Need this to run once view is displayed.
}
Try using ViewTreeObserver as follow:
final View yourView = View.inflate(....);
ViewTreeObserver observer = yourView .getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// Do what you need with yourView here...
}
});
Notice that the function removeGlobalOnLayoutListener(this) is different in some sdk versions.