I'm trying to create a memory game - where I have 5x5 images on the screen and the user has to match the images.
I've been using a GridView and populate it with images using a ImageAdapter.
The game works something like this:
- when a user matches 2 images - the images remain on the screen
- when a user fails to match the 2 pictures - the imagess changes back to the question-mark.
The problem is that I can't manage to keep the pictures previously matched on the screen - when I use the notifyDataSetChanged() method - all the screen is filled again with question marks.
Here's my code:
// getView method in ImageAdapter
public View getView(int position, View convertView, ViewGroup arg2) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(100, 100));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(4, 4, 4, 4);
} else {
imageView = (ImageView) convertView;
}
for(int i=0;i<16;i++)
{
if(mThumbIds[i].equals(R.drawable.ic_launcher))
{
imageView.setImageResource(R.drawable.ic_launcher);
}
else
imageView.setImageResource(R.drawable.q_mark);
}
return imageView;
}
// the onClickListener when a user selects 1 image
gridview.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
i++;
Toast.makeText(easyGame.this, "" + position, Toast.LENGTH_SHORT).show();
ImageView imgV=(ImageView)v;
if(i%2!=0)
{
firstClick=position;
imgV.setImageResource(ImageAdapter.mThumbIds[firstClick]);
}
else
{
secondClick=position;
imgV.setImageResource(ImageAdapter.mThumbIds[secondClick]);
}
if(i%2==0)
{
if(!(ImageAdapter.mThumbIds[firstClick].equals(ImageAdapter.mThumbIds[secondClick])))
{
Toast.makeText(easyGame.this, "Great!", Toast.LENGTH_SHORT).show();
ImageAdapter.mThumbIds[firstClick]=ImageAdapter.mThumbsIdsDone[0];
ImageAdapter.mThumbIds[secondClick]=ImageAdapter.mThumbsIdsDone[0];
im.notifyDataSetChanged();
gridview.setAdapter(im);
gridview.invalidate();
}
}
}
});
Can anyone help? Thanks!
// declare as class variable to keep track of views which should stay visible
private HashSet<Integer> keepVisibleViews = new HashSet<Integer>(25);
//in you on click listener
if(!(ImageAdapter.mThumbIds[firstClick].equals(ImageAdapter.mThumbIds[secondClick])))
{
// ... the rest of your code here
// keep track of views that should stay visible
keepVisibleViews.add(firstClick);
keepVisibleViews.add(secondClick);
}
// in your getView
// show ? mark if we should, else the picture
if (keepVisibleViews.contains(position)
setImageResource(ImageAdapter.mThumbIds[secondClick]);
else
imageView.setImageResource(R.drawable.q_mark);
Related
I am writing a photo picker for Facebook inside my app, i have a recyclerview with a grid layout and i want to prevent for scrolling up, i was able to do this by using scrollToPosition and this works but not the way i want
Problem
When i click in a photo on the 2 row that row jumps to the top and becomes the number 1 visible row, if i click the 3 row the samething happens.
I don't want the recycler to move if the view is visible it should remain the same, so if i click on a a photo that is on the last visible row i want the scroll to stay the same, i don't want it to make the last row the first.
Tries to solve it
I tried several things to fix this, i tried calling setNestedScrollingEnabled i followed this How to disable RecyclerView scrolling?
public static void onItemClick(int position){
//picker.setNestedScrollingEnabled(false);
for(int k = 0; k<photoBag.size();k++) {
if(k == position)
photoBag.set(position, new PhotoBag(photoBag.get(position).getPhoto(), true)); //Here im marking the photo to selected
else
photoBag.set(k, new PhotoBag(photoBag.get(k).getPhoto(), false));//Here im setting unselecting all the other photos
}
picker.setAdapter(adapter);
adapter.notifyDataSetChanged();
picker.scrollToPosition(position);
//Log.d("FacebookPicker", "position " + grid.findFirstCompletelyVisibleItemPosition());
//picker.setNestedScrollingEnabled(true);
}
I thought that maybe disabling the scroll would lock the recyclerview on the corrent position but it didn't jumps right up.
I also tried getting the Vertical offset and set it after calling notifyDataSetChange but i can't find a way to set the offset programmatically
EDIT
Adapter
class PickerAdapter extends RecyclerView.Adapter<PickerAdapter.PickerAdapterHolder> {
public final String TAG = "PickerAdapter";
private ArrayList<PhotoBag> photoBag;
private Context context;
private OnClickListener onClickListener;
class PickerAdapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView photo;
ImageView imageBorder;
PickerAdapterHolder(View view) {
super(view);
photo = (ImageView) view.findViewById(R.id.photoItem);
photo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.photoItem:
FacebookPhotoPicker.onItemClick(getAdapterPosition()); //i know that there are better ways to get the clicked item from other class but since im still debuging i don't need to worry about performace i just need it to work
break;
}
}
}
PickerAdapter(Context context, ArrayList<PhotoBag> itemList) {
this.photoBag = itemList;
this.context = context;
}
#Override
public PickerAdapterHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.facebook_picker_item, null);
return new PickerAdapterHolder(layoutView);
}
#Override
public void onBindViewHolder(final PickerAdapterHolder holder, final int position) {
if(photoBag.get(position).isSelected()){
int border = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics()));
Bitmap photo = photoBag.get(position).getPhoto();
photo = Bitmap.createScaledBitmap(photo,photo.getWidth() - (border*2), photo.getHeight() - (border*2), false);
photo = addWhiteBorder(photo,border);
holder.photo.setImageBitmap(photo);
}else {
holder.photo.setImageBitmap(photoBag.get(position).getPhoto());
}
}
#Override
public int getItemCount() {
return this.photoBag.size();
}
private Bitmap addWhiteBorder(Bitmap bmp, int borderSize) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bmp.getWidth() + borderSize * 2, bmp.getHeight() + borderSize * 2, bmp.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmp, borderSize, borderSize, null);
return bmpWithBorder;
}
remove those 2 lines from onItemClick
picker.setAdapter(adapter);
picker.scrollToPosition(position);
every time you setAdapter it resets position, and now you don't need to set a new position again.
this should work. If it doesn't, check this answer of mine (and their comments) about providing ID How to remain at a scroll position in RecyclerView after adding items at its first index and call notifydatasetchange
I'm very confusing with this.
Question is simple, I'm trying to resize the ImageView height, to do, I get the display width and add on it 0.25 of percentage.
Problem, if I set the new value of height outside of the post() ImageView method, the position parameter is deliver in getView() wrong. If I do it inside post() the first elements showed are not rescaled.
Read comments inside code.
public static class ViewHolder {
ImageView imgAvatar;
public void loadAvatar(SocialService socialService, long userId) {
try {
// SocialService.loadAvatar(..) is working with UniversalImageLoader.
socialService.loadAvatar(this.imgAvatar, userId, true, false);
} catch (Exception ex) {
Log.e("APPERROR", ex.getMessage());
}
}
}
#Override
public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
final User user = this.users.get(position);
if (view == null) {
holder = new ViewHolder();
view = inflater.inflate(R.layout.adapter_list, parent, false);
holder.imgAvatar = (ImageView) view.findViewById(R.id.people_list_avatar);
// With this commented snippet of code, the first 4 elements (showed in the top of the listview) are not rescaled.
// Position is deliver ok.
// The rest of elements that are going showing while scrolling works pretty fine.
// If scroll down and come back to the top then the 4 top first elements are showing rescaled.
/*final ImageView imgAvatarAux = holder.imgAvatar;
holder.imgAvatar.post(new Runnable() {
#Override
public void run() {
imgAvatarAux.getLayoutParams().height =
(int) ((Display.deviceWidth(PeopleAdapter.this.context) / 2) * 1.25F);
}
});*/
view.setTag(holder);
// HERE IS THE QUESTION.
// If I remove this line of code, position is deliver ok, but if it works, position is deliver senseless. WHY?
holder.imgAvatar.getLayoutParams().height = (int) ((Display.deviceWidth(PeopleGridAdapter.this.context) / 2) * 1.25F);
holder.loadAvatar(this.socialService, user.getId());
}
holder = (ViewHolder) view.getTag();
...
if (position == 0) {
// An interface method for another purposes...
this.waitFinish.onFinish();
}
return view;
}
Post your ListView Item layout adapter_list. Code fix [operations on list items outside if(){}else{}]:
// HERE IS THE QUESTION.
// If I remove this line of code, position is deliver ok, but if it works, position is deliver senseless. WHY?
holder.imgAvatar.getLayoutParams().height = (int) ((Display.deviceWidth(PeopleGridAdapter.this.context) / 2) * 1.25F);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.loadAvatar(this.socialService, user.getId());
I have a 2x27 gridview that's holding white imageviews. I then apply a colorfilter to the imageviews, and every cycle invalidate them. The end result is some animated colors moving around the gridview.
I'm having problems with the gridview randomly showing a white square, instead of the black I initalize everything to. Then, as the colors change every imageview updates, except for the ones that failed to load properly the first time. They always remain white.
Sometimes this doesn't happen. Sometimes it's 1 square, sometimes it's 4 squares.
If I rotate my screen, the stuck white ones go away and everything works. They never come back no matter how many times I rotate/leave the activity and return.
This is me updating the colorfilters. I've checked in here that every grid is getting updated, nothing remains white.
public void updateColors() {
Runnable runnable1 = new Runnable() {
#Override
public void run() {
int r = 0;
int g = 0;
int b = 0;
int y = 0;
while(keepRunning == true){
y = 0;
for(int x=0;x<Consts.VIEWCOUNT;x++){
g = Buffers.offLineBuffer_[y++];
r = Buffers.offLineBuffer_[y++];
b = Buffers.offLineBuffer_[y++];
Drawable myIcon = getResources().getDrawable( ImageAdapter.mThumbIds[x]);
myIcon.setColorFilter(new PorterDuffColorFilter(Color.rgb(r, g, b),PorterDuff.Mode.MULTIPLY));
}
try {
Thread.sleep(55);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
#Override
public void run() {
gridView.invalidateViews();
}
});
}
}
};
new Thread(runnable1).start();
}
This is my adapter
#Override
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(33, 33));
} else {
imageView = (ImageView) convertView;
}
imageView.setImageResource(mThumbIds[position]);
return imageView;
}
mThumbsID is just a list of ID's to drawables like below (I shortened it)
public static Integer[] mThumbIdsTemp = {
R.drawable.image1,
R.drawable.image2,
R.drawable.image3,
R.drawable.image4,
}
It just started doing this one day, I haven't been able to track it back to what is causing it, or why it's only ever on the first time it's loaded, but will remain until it's destroyed via rotating the screen.
I've lost over a day trying to track this down, and I'm lost as to what might be causing it.
Any ideas?
I'm using this example of gridview http://developer.android.com/resources/tutorials/views/hello-gridview.html just with less images and a rotate button in one corner. I want to click a image, then click rotate button to rotate that image, but I can't find how to do it.
Anyone have any idea?
Rotating the image can be done using setImageMatrix() within getView if you keep track of which images you want rotated. Something like this:
HashMap<Integer, Matrix> mImageTransforms = new HashMap<Integer,Matrix>();
Matrix mIdentityMatrix = new Matrix();
// 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(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
Matrix m = mImageTransforms.get(position);
if ( null == m ) {
m = mIdentityMatrix;
}
imageView.setImageMatrix(m);
imageView.setImageResource(mThumbIds[position]);
return imageView;
}
void setImageRotation(int position, int degrees) {
Matrix m = mImageTransforms.remove(position);
if ( degrees != 0 ) {
if ( null == m ) m = new Matrix();
m.setRotate(degrees);
mImageTransforms.put(position, m);
}
notifyDataSetChanged();
}
One thing to note. In touch mode lists and grids do not have a selection per-se, however they can be checked. It's a bit tricky to get a grid view to show the checked state, that being said the Honeypad Tutorial shows how to use checked views within a list view which may be helpful.
Without starting from the middle of the stack of images how do I rotate them in the infinite loop left to right and right to left? I tried setSelection(position) but for some reason I get that method called few times and inconsistently. My images increment has to be saved int he app state so it makes it a bit more complicated.
#Override
public void setSelection(int position){
int sectionPos = getCurrentPositionFromState();
if (sectionPos == (this._images - 1)){
setCurrentPositionFromState(0);
sectionPos = 0;
}
else {
setCurrentPositionInState(sectionPos +1);
}
if (sectionPos <= (this._images - 1) ){
super.setSelection(sectionPos);
}
}
gallery.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView parent, View view, int position, long id) {
gallery.setSelection(position);
#Override
public void onNothingSelected(AdapterView parent) {
}
});
I should also mention that I have an onFling() overriden like so:
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return super.onFling(e1, e2, 0, velocityY);
}
There are a few ways to go about it, but in all cases, it's never technically "infinite." I got the basis of mine from snooping around for a while.
First we need to put a gallery into the xml file in the layout. So after creating the file put in a little snippet like this:
<!-- Gallery To show images Gallery -->
< Gallery
android:id="#+id/galleryView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingBottom="15dip"/>
Now lets set up a Vector of different images so we can loop through. We will be getting these images from our resources, but if you want to get them from somewhere else then just insert that instead. We are going to insert this into onCreate(). This will allow us to have a vector of id's that we can reference when getting images to put into the gallery later:
public Vector<Integer> mPhotoVector = new Vector<Integer>();
public void setPhotos() {
for(as many photos as you want){
int imageResource = getResources().getIdentifier("imageNAme", "drawable", getPackageName());
mPhotoVector.add(imageResource);
}
Where galView is my gallery view from the layout. And as we finish up this project we need to call the gallery view from the xml and initialize it to a gallery variable we will have in the code.
LoopingGalleryAdapter adapter;
Gallery galView;
adapter = new LoopingGalleryAdapter(this, mPhotoVector);
galView = (LoopingGallery)mLayoutView.findViewById(R.id.galleryView);
galView.setAdapter(adapter);
galView.setSelection((galView.getCount() / 2));
The setSelection() will have us looking at the middle of the gallery, this makes it appear to be "infinite" since there are now 1073741823 elements to each side.
Next we need to create the adapter. The basis is to make the largest gallery you can, so next we just add in our own little getCount method. This will create a gallery that is too large for the user to (plausibly) scroll to the end of.
Lastly on the adapter we have the meat of the project which requires us to set where our position is. This is the key. Once the position is set to the middle-ish of the gallery, it seems as though you have an infinite loop of images on either side.
I started mine off a little like this:
public class LoopingGalleryAdapter extends BaseAdapter {
private ImageView iv;
private Context mContext;
public PhotoVector mPhotoVector = null;
int mGalleryItemBackground;
public LoopingGalleryAdapter(Context c, PhotoVector aVector) {
this.mContext = c;
mPhotoVector = aVector;
}
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public Object getItem(int position) {
return position;
}
public ImageView getImage(){
return iv;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
iv = new ImageView(mContext);
private final int middle = 1073741823; //this is the middle index of the gallery in galView
/************************************************************************
*if you have a vector/array/arrayList of photos you would like to display
************************************************************************/
if((position - middle) >= 0) { relativePosition = (position-middle) % mPhotoVector.size(); }
else { relativePosition = mPhotoVector.size() - (Math.abs(position - middle) % mPhotoVector.size()); }
Drawable draw = mContext.getResources().getDrawable(mPhotoVector.get(relativePosistion));
/*********************************************
*otherwise you can just insert a photo like so
*********************************************/
Drawable draw = mContext.getResources().getDrawable(R.drawable.what_you_want);
iv.setImageDrawable(draw);
return iv;
}
}
And we are now done!
Additionally, one of the tricks I have found quite useful, if not necessary when getting images from the internet is to keep a count of how many times you have gone through getView() and after 10-20 times clear your cache so you don't throw a OutOfMemoryError by doing:
if(counter >= 20){
galView.destroyDrawingCache();
counter = 0;
}
Or have getView() throw a OutOfMemoryError, catch it and return an empty ImageView (iv) and then clear then call galView.destroyDrawingCache();
A shameless self plug, just wrote an Infinite Scrolling Gallery tutorial:
http://blog.blundellapps.com/infinite-scrolling-gallery/
Source code can be downloaded also, you choose the image size.
You can use images on your SD card or images in your /resources/drawable directory.