I have a GridView where each cell is a thumbnail, a filename and a checkbox. To populate the grid I use a CustomCursorAdapter:
The CustomCursorAdapter extends CursorAdapter.
I have a floating button that launchs the system camera. If I take a picture, the grid updates correctly with the new cell.
In the action bar I have a delete button. If I check one or more checkboxes and press the delete button the cell is/are correctly deleted.
But then I have also a ShareActionProvider button so you can check multiple cells and share them. And that works. BUT, here comes the problem. Imagine we have 3 or 4 cells. You check a couple of them, share them, and when you come back from the sharing dialog you see all the cells and you can see how all of them fade and disappear but one, the upper leftmost one.
I checked and saw that after the sharing dialog is the onResume method the one executed. The only thing I'm interested to be done in that method is to reset the checkboxes (a selected.clear() of the variable and a cb.setChecked(false) of each Checkbox view). Also I set the intent of the ShareActionProvider button for a empty selection (this will trigger a Toast: "Select first the images to share" if the button is pressed now).
#Override
public void onResume() {
if (shared) { //we come from a share
uncheck(); //unchecks the Checkboxes views
selected.clear(); //reset the selected items list
//getShareIntent returns an intent with the items in
//selected (now empty) as extras
Intent intent = this.getShareIntent();
if (intent != null && mShareActionProvider != null) {
mShareActionProvider.setShareIntent(Intent.createChooser(intent,
getResources().getText(R.string.send_to)));
}
shared = false;
}
super.onResume();
}
Removing the unchecking, reset and new intent setting don't affect to that behaviour, the cells disappear anyway. In fact, if I comment the whole onResume function the problem remains.
I tried with things like:
grid.invalidateViews();
mAdapter.notifyDataSetChanged();
grid.setAdapter(mAdapter);
Also tried and put the super as the first line, but then checkboxes, intent and list variable aren't updated.
Another clue is that if I press the thumbnail of the unique cell left visible (this loads a new activity to see the picture big size), then the disappeared cells appear for a second while the new activity is loading. o_O
I tried to press the places where the thumbnails should appear after the share to check if it launches the big display activity anyway, but that doesn't happen.
I read about similar things when scrolling GridViews but I just have three elements, no need to scroll. What could be happening here?
EDIT: To check if the issue was related to the memory and the thumbnails, I commented them in the item views, letting just the textview and the checkbox. But the problem is still there. Absolutely desperate -I've been days trying to fix this-I tried to relaunch the grid activity in the onResume, to try to force the redraw (while mAdapter.notifyDataSetChanged() worked when adding a new cell, never worked after the sharing):
#Override
public void onResume() {
super.onResume();
if (shared) {
shared = false;
Intent intClearStack = new Intent(
getBaseContext(),
GridActivity.class);
intClearStack
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intClearStack);
finish();
}t
}
I'm not proud of this solution but is the nearest thing I found to fix this. Nearest because, while it works almost all the time, ocasionally it doesn't. I'd like to know why there's a difference when it comes back from a ShareActionProvider activity and any other activity, to try to find out why this is acting like that.
Related
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
I have a list of products, if I click on one, the image of the product is transitioned into the detail screen.
And if I go back, the image is transitioned back to the list.
This works fine.
The problem is that when I scroll down in my detail screen, the image is no longer visible.
But when I go back to the list screen the image is still transitioned, resulting is a buggy transition.
Video example here
I want to achieve something like the Play Store
Where there is no return animation if the image is no longer visible.
Code
Starting detail activity:
Intent intent = new Intent(getActivity(), ProductDetailActivity.class);
intent.putExtra(ProductDetailActivity.EXTRA_PRODUCT, product);
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
productViewHolder.getProductCover(), productViewHolder.getProductCover().getTransitionName()).toBundle();
getActivity().startActivity(intent, options);
In DetailActivity I set the transition name:
coverImageView.setTransitionName(getString(R.string.transition_key_product_cover_with_id, product.getId()));
styles.xml:
<item name="android:windowContentTransitions">true</item>
Any idea how to implement the behaviour I want to achieve?
With the following link you can know if a view inside an scroll is visible or not: https://stackoverflow.com/a/12428154/4097924
Then you can make a simple method to know if your imageView is visible inside the scrollview, similar to this easy example:
public boolean isVisibleInsideScroll(){
Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (imageView.getLocalVisibleRect(scrollBounds)) {
// Any portion of the imageView, even a single pixel, is within the visible window
return true;
} else {
// NONE of the imageView is within the visible window
return false;
}
}
Then I see two possible options that I have not proved:
option 1: overwrite the method onBack (and every way to go back if you have another one). Inside the method, you can assign the transition when the element it is visible before leaving the screen:
#Override
public void onBackPressed(){
if(isVisibleInsideScroll()){
coverImageView.setTransitionName(getString(R.string.transition_key_product_cover_with_id, product.getId()));
}
super.onBackPressed();
}
option 2: overwrite the method onScroll (and every time the scrollview is scrolled) you can register or unregister the animation if the view is visible.
The code in this option is similar to the previous one.
Good luck! I like a lot your animation, I saw it in youtube. :)
I am creating my first app. In this app, I have an expandable list with many items. When I select any of these items, I want several paragraphs to be displayed. Do I need to create an Activity for each of these items if text is the only thing I want displayed? I know that there has to be an easier way. I did create it like this at first and it seemed very bulky (30+ activities), so now I have it set up so that when an item is selected, the setContentView opens the corresponding layout with the text that needs to be displayed. This works but there is a catch, whenever I hit the back button, it takes me back to my main activity class and not my expandable list class. I want the user to be able to go back and select something else from the list. Any guidance as to what I need to do would be appreciated.
I would suggest creating string resources for each item you would like to display, then creating one activity with a TextView. Then, instead of creating new intents for each activity, create an intent that goes to the new activity, and add an extra that contains the text for the TextView. For example:
Activity1:
myButton.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
Intent intent = new Intent(this, ParagraphView.class);
intent.putExtra("textData", getResources().getString(R.string.myText));
getBaseContext().startActivity(intent);
}
});
In the onCreate of the viewer, add this to get your TextView:
Intent intent = getIntent();
String textData = intent.getStringExtra("text");
Now, we need to write the text into the TextView:
TextView tv = (TextView) findViewById(R.id.myTextView);
tv.setText(textData);
All you have to to is set up your string resources and button click listeners. You may consider this easier than having lots of activities (it's definitely easier to manage entries this way) but does require a bit of setup.
Edit: Thanks to #ianhanniballake for pointing out a much better way (I don't even know what I was thinking at the time...)
Edit2: Wow, I REALLY messed up my code. (Hopefully) Fixed now
I am having an app which contains number of list views. That means One listview opens another listiview on click of an item from first listview. Then it opens third one, on click of item from second list and so on. Now problem here is that the listview takes some time to populate data, giving user a feel that the click is not acknowledged and user presses it again. Due to this the first click opens the 2nd list and 2nd click opens third list, which is not desired. How can I take care of this. I tried using listview.setEnabled(false) but that doesn't help. I don't want to show a progress bar. Can i disable the 2nd click or any other way to handle this problem?
Set a global boolean $bBeenClicked, make it true on the first click and check it on the second; set it back to false when the list has finished loading.
You should look into enhancing the performance of your ListViews. Unless they need to access something over the internet or have some other similar constraint, they shouldn't take so long to load. A good ListView should only load what can be shown on the screen and then load more as needed when the user scrolls down. There are a few techniques you can use like making sure you reuse the convertView aswell. I would recommend that you watch this video. It goes into the best ways to implement ListView so that it performs well.
You could add haptic feedback for when the user clicks on a listview item to tell the user that it was clicked.
How to enable haptic feedback on button view
You shoudl load the new List in a PpgressDialog:
public static void open_new_list(int level)
{
verlauf = ProgressDialog.show(ctx, "Wait...", "...load new List",true,false);
new Thread(){
#Override
public void run(){
Looper.prepare();
//load the new Listview
//display via runnable
}
verlauf.dismiss();
}
}.start();
}
edit:
Without Progressbar you might remove the adapter after klicking, then the user isn't able to klick twice
I've been messing with some layouts for my ListActivity's ListView and found a problem:
When I click a list item it starts a new Activity. Unfortunately, when I click the back button to go back to my ListActivity, the scrollbar of the ListView is all the way at the top.
It used to be that, when I clicked the back button, the user would be scrolled to the same place in the list as they were before they selected the list item to launch a new screen.
I'm pretty sure I've removed all of the code I've changed, but the problem is still occurring. Any ideas?
If you're hitting on create in your code your list most likely is recreated and therefore position is not saved. Try to rotate device while in the ListActivity ( scroll to the middle and rotate ) and see if you see the problem . I'd cache the postion of the list and scroll List to it.
Example of such things :
http://developer.android.com/resources/tutorials/notepad/notepad-ex3.html
I figured out why this is happening
#Override public void onStop() {
super.onStop();
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
mAdapter.changeCursor(null);
}
I was told this is something I'm "supposed" to do. I think I can understand why. However, it causes the undesirable behavior I'm seeing here.