Related
I am trying to make a slide show using viewPager in Android Studio. I use BitmapFactory.Options to resize images if they are too "large", also I use in manifest the following code android:largeHeap="true"
android:allowBackup="true"
I want to specify that the above code does not work in my case, if I not use BitmapFactory.Options to resize the images I get the error java.lang.OutOfMemoryError.
Here is the code for adapter
public class SlideShowAdapter extends PagerAdapter {
private Context context;
private LayoutInflater inflater;
private final int HEIGHT_IMGAE = 230;
private int [] images = {
R.drawable.img1,
R.drawable.img2,
R.drawable.img3
};
public SlideShowAdapter(Context context) {
this.context = context;
}
#Override
public int getCount() {
return images.length;
}
#Override
public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return (view == object);
}
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.slide_show_layout, container, false);
ImageView img = view.findViewById(R.id.sile_show_layout_imageView);
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
img.setImageBitmap(
decodeSampledBitmapFromResource(context.getResources(), images[position], width, HEIGHT_IMGAE));
container.addView(view);
return view;
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
container.removeView((LinearLayout)object);
}
private static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
private static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
Everything works fine in main
public class MainActivity extends AppCompatActivity {
private ViewPager slideShow;
private SlideShowAdapter adapter ;
private int currentPage = 0;
Timer timer;
final long DELAY_MS = 3000; //delay in milliseconds before task is to be executed
final long PERIOD_MS = 5000; // time in milliseconds between successive task executions.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
slideShow = findViewById(R.id.main_activity_viewPager_slideshow);
adapter = new SlideShowAdapter(this);
slideShow.setAdapter(adapter);
this.doSlideShow();
}
public void doSlideShow(){
final Handler handler = new Handler();
final Runnable Update = new Runnable() {
public void run() {
if (currentPage == adapter.getCount()) {
currentPage = 0;
}
slideShow.setCurrentItem(currentPage++, true);
}
};
timer = new Timer(); // This will create a new Thread
timer.schedule(new TimerTask() { // task to be scheduled
#Override
public void run() {
handler.post(Update);
}
}, DELAY_MS, PERIOD_MS);
}
public void luchAdminActivity(View view) {
startActivity(new Intent(this,SingupActivity.class));
}
public void luchMainActivity(View view) {
startActivity(new Intent(this,MainActivity.class));
}
public void luchMenuActivity(View view) {
startActivity(new Intent(this,MainActivity.class));
}
}
but when I go to another Activity and try to return to Main Activity(where I have the slide show) I get the following error
Process: bogdan.yourwaiter, PID: 25647
java.lang.OutOfMemoryError: Failed to allocate a 156487692 byte allocation with 16768376 free bytes and 114MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:700)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:535)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:558)
at bogdan.yourwaiter.adapter.SlideShowAdapter.decodeSampledBitmapFromResource(SlideShowAdapter.java:111)
at bogdan.yourwaiter.adapter.SlideShowAdapter.instantiateItem(SlideShowAdapter.java:61)
at androidx.viewpager.widget.ViewPager.addNewItem(ViewPager.java:1010)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1224)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1092)
at androidx.viewpager.widget.ViewPager.onMeasure(ViewPager.java:1622)
at android.view.View.measure(View.java:21121)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6462)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1464)
at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1117)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:642)
at android.view.View.measure(View.java:21121)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:715)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:461)
at android.view.View.measure(View.java:21121)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6462)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
at android.view.View.measure(View.java:21121)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6462)
at androidx.appcompat.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:401)
at android.view.View.measure(View.java:21121)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6462)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.view.View.measure(View.java:21121)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6462)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1464)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:758)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:640)
at android.view.View.measure(View.java:21121)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6462)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:872)
at android.view.View.measure(View.java:21121)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2625)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1677)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1928)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1550)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7190)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:959)
at android.view.Choreographer.doCallbacks(Choreographer.java:734)
at android.view.Choreographer.doFrame(Choreographer.java:670)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:945)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)
10-16 21:49:03.318 29852-29852/com.klau.jobsinaustralia I/dalvikvm-heap: Grow heap (frag case) to 171.874MB for 14400016-byte allocation
10-16 21:49:03.578 29852-29852/com.klau.jobsinaustralia I/dalvikvm-heap: Grow heap (frag case) to 175.314MB for 14400016-byte allocation
10-16 21:49:03.689 29852-29852/com.klau.jobsinaustralia I/dalvikvm-heap: Grow heap (frag case) to 177.603MB for 3600016-byte allocation
10-16 21:49:03.839 29852-29852/com.klau.jobsinaustralia I/dalvikvm-heap: Grow heap (frag case) to 178.716MB for 14364016-byte allocation
10-16 21:49:04.029 29852-29852/com.klau.jobsinaustralia I/dalvikvm-heap: Grow heap (frag case) to 182.185MB for 14400016-byte allocation
10-16 21:49:04.139 29852-29852/com.klau.jobsinaustralia I/dalvikvm-heap: Forcing collection of SoftReferences for 3600016-byte allocation
10-16 21:49:04.199 29852-29852/com.klau.jobsinaustralia E/dalvikvm-heap: Out of memory on a 3600016-byte allocation.
10-16 21:49:04.199 29852-29852/com.klau.jobsinaustralia I/dalvikvm: "main" prio=5 tid=1 RUNNABLE
10-16 21:49:08.223 29852-29852/com.klau.jobsinaustralia E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.OutOfMemoryError: (Heap Size=194912KB, Allocated=182627KB)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:628)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:475)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:504)
at com.klau.jobsinaustralia.scrollable.CustomList.decodeSampledBitmapFromResource
Activity
public class ExtraInfoActivity extends BaseActivity {
ListView list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
final String languageToLoad = sharedPreferences.getString("PREF_LIST", "no selection");
Locale locale = new Locale(languageToLoad);
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,getBaseContext().getResources().getDisplayMetrics());
setContentView(R.layout.activity_extra_info);
String[] nsw_attraction = {
"Sydney Bridge",
"Blue Mountains",
"Bondi Beach",
"The Hunter Valley",
"The Jenolan Caves"
};
final String[] nsw_web = {
getResources().getString(R.string.nsw_attraction_1),
getResources().getString(R.string.nsw_attraction_2),
getResources().getString(R.string.nsw_attraction_3),
getResources().getString(R.string.nsw_attraction_4),
getResources().getString(R.string.nsw_attraction_5)
};
Integer[] nswImageId = {
R.drawable.nsw_attraction_bridge,
R.drawable.nsw_attraction_blue_mountains,
R.drawable.nsw_attraction_bondi_beach,
R.drawable.nsw_attraction_hunter_valley,
R.drawable.nsw_attraction_jenolan_caves
};
CustomList adapter = new CustomList(ExtraInfoActivity.this, nsw_attraction, nsw_web, nswImageId);
list = (ListView) findViewById(R.id.list);
list.setAdapter(adapter);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(ExtraInfoActivity.this, nsw_web[+position], Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onStart() {
super.onStart();
try {
mTracker.setScreenName("Extra Infomation");
mTracker.send(new HitBuilders.AppViewBuilder().build());
}catch (Exception ex){
ex.printStackTrace();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_home, menu);
return true;
}
}
You need to use a library to load the images in the ListView.
Glide is an amazing library to load the images.
It's setup is very simple:
Put these two in your build.gradle:
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:support-v4:19.1.0'
}
and then in your adapter all you need to do is:
Glide
.with(context)
.load(nswImageId[position])
.into(myImageView);
That's it, it will take care of everything. You can find more information and usage details here.
I have already resized the image. my CustomList Class below
public class CustomList extends ArrayAdapter{
private final Activity context;
private final String[] placeName;
private final String[] web;
private final Integer[] imageId;
public CustomList(Activity context, String[] placeName, String[] web, Integer[] imageId) {
super(context, R.layout.list_single, web);
this.context = context;
this.placeName = placeName;
this.web = web;
this.imageId = imageId;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView= inflater.inflate(R.layout.list_single, null, true);
TextView place = (TextView) rowView.findViewById(R.id.place_name);
place.setText(placeName[position]);
TextView txtTitle = (TextView) rowView.findViewById(R.id.txt);
txtTitle.setText(web[position]);
ImageView imageView = (ImageView) rowView.findViewById(R.id.img);
Bitmap bitmap = decodeSampledBitmapFromResource(context.getResources(), imageId[position], 150, 150);
if (bitmap != null) {
bitmap.recycle(); //<====== ERROR here: java.lang.IllegalArgumentException: Cannot draw recycled bitmap.
}
imageView.setImageBitmap(bitmap);
return rowView;
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
Use Picasso Library.Its so easy to use and handle many other error also.
http://square.github.io/picasso/
many thanks for a quick answer. Not sure i can do it correctly. please reiew my changes.
#Override
public View getView(int position, View view, ViewGroup parent) {
final ImageView myImageView;
LayoutInflater inflater = context.getLayoutInflater();
View rowView= inflater.inflate(R.layout.list_single, null, true);
TextView place = (TextView) rowView.findViewById(R.id.place_name);
place.setText(placeName[position]);
TextView txtTitle = (TextView) rowView.findViewById(R.id.txt);
txtTitle.setText(web[position]);
ImageView imageView = (ImageView) rowView.findViewById(R.id.img);
if (view == null) {
myImageView = (ImageView) inflater.inflate(imageView, parent, false);
} else {
myImageView = (ImageView) recycled;
}
Glide
.with(context)
.load(nswImageId[position])
.into(myImageView);
return myImageView;
}
write this line in application tag in your manifest file
android:largeHeap="true"
i'm having an issue working with CardViews . I`ve created a CardView containing 1 TextView and 2 ImageViews , which gets displayed in an RecycleView . When i try to update the contents of one of the CardViews (created 5 cards for testing) the interface starts to lag when scrolling over the updated card, but goes back to normal when i pass the item. It only happens when ImageViews are present , when substituting the ImageViews for say, TextViews with some random text in them, it works normally.
Now, here's my code , after that i'll add some more information that could be causing it.
First , the Adapter class, which also contains the items class and List :
public class MovieDetailsAdapter extends RecyclerView.Adapter<MovieDetailsAdapter.MovieViewHolder> {
public List<MovieDetails> movieList;
private static final String TAG = "MyAdapter";
public MovieDetailsAdapter() {
movieList = new ArrayList< MovieDetails>();
}
public void setItemCount(int count ,String title, Bitmap poster,Bitmap fanart ) {
movieList.clear();
movieList.addAll(generateDummyData(count, title, poster , fanart));
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return movieList.size();
}
public void addItem(int position,String title, Bitmap poster, Bitmap fanart) {
if (position > movieList.size()) return;
movieList.add(position, generateDummyItem(title, poster, fanart));
// notifyDataSetChanged();
notifyItemInserted(position);
}
public void updateItem(int position, String title, Bitmap poster, Bitmap fanart) {
if (position > movieList.size()) return;
movieList.add(position, generateDummyItem(title, poster, fanart));
notifyItemChanged(position);
// notifyDataSetChanged();
}
#Override
public MovieViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.card_layout_movies, viewGroup, false);
Log.d(TAG, "create header view holder");
return new MovieViewHolder(itemView);
}
#Override
public void onBindViewHolder(MovieViewHolder movieViewHolder, int i) {
Log.d(TAG, "bind header view holder");
MovieDetails mdet = movieList.get(i);
movieViewHolder.vTitle.setText(mdet.Title);
movieViewHolder.vPoster.setImageBitmap(mdet.imageViewPoster);
movieViewHolder.vFanart.setImageBitmap(mdet.imageViewFanart);
Log.d(TAG, "position: " + i + " holder: " + movieViewHolder + " text: " + movieViewHolder.vTitle);
}
public static class MovieViewHolder extends RecyclerView.ViewHolder {
protected TextView vTitle;
protected ImageView vPoster;
protected ImageView vFanart;
public MovieViewHolder(View v)
{
super(v);
vTitle = (TextView) v.findViewById(R.id.title);
vPoster = (ImageView) v.findViewById(R.id.imageViewPoster);
vFanart = (ImageView) v.findViewById(R.id.imageViewFanart);
}
}
public static class MovieDetails {
protected String title;
protected Bitmap imageViewPoster;
protected Bitmap imageViewFanart;
public MovieDetails(String title, Bitmap imageViewPoster,Bitmap imageViewFanart )
{
this.title = title;
this.imageViewPoster = imageViewPoster;
this.imageViewFanart = imageViewFanart;
}
}
public static MovieDetails generateDummyItem(String title, Bitmap poster, Bitmap fanart) {
MovieDetails mov = new MovieDetails(title, poster, fanart);
return mov;
}
public static List< MovieDetailsAdapter.MovieDetails> generateDummyData(int count, String title , Bitmap imageViewPoster, Bitmap imageviewFanart) {
ArrayList<MovieDetailsAdapter.MovieDetails> items = new ArrayList<MovieDetailsAdapter.MovieDetails>();
for (int i=0; i < count; i++) {
items.add(new MovieDetailsAdapter.MovieDetails(title, imageViewPoster, imageviewFanart));
}
return items;
}
}
Now, here is my main class , the activity class
public class MoviesListActivity extends AppCompatActivity {
public MovieDetailsAdapter ca = new MovieDetailsAdapter();
public RecyclerView recList;
private Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movies_list);
RecyclerView recList = (RecyclerView) findViewById(R.id.cardList);
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(LinearLayoutManager.VERTICAL);
recList.setLayoutManager(llm);
button = (Button) findViewById(R.id.button);
MyOnClickListener Listener = new MyOnClickListener();
button.setOnClickListener(Listener);
recList.setItemAnimator(null);
Bitmap bittest1 = decodeSampledBitmapFromResource(getResources(), R.drawable.poster_example, 640, 955);
Bitmap bittest2 = decodeSampledBitmapFromResource(getResources(), R.drawable.fanart_example, 800, 450);
ca.setItemCount(5, "TestingTitle", bittest1, bittest2);
recList.setAdapter(ca);
}
public class MyOnClickListener implements View.OnClickListener {
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
Bitmap bittest12 = decodeSampledBitmapFromResource(getResources(), R.drawable.poster2, 640, 955);
Bitmap bittest22 = decodeSampledBitmapFromResource(getResources(), R.drawable.fanart2, 800, 450);
Toast.makeText(getApplicationContext(), "msg msg", Toast.LENGTH_SHORT).show();
ca.updateItem(1, "test", bittest12, bittest22);
// ca.addItem(1,"test",bittest12,bittest22);
break;
default:
break;
}
}
}
//------methods used for resizing the images down to smaller ones before loading into memory---//
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
And finally, not that i probably matters, my CardView XML.
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
card_view:cardCornerRadius="4dp"
android:layout_margin="5dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="#color/bkg_card"
android:text="Movie Title"
android:gravity="center_vertical"
android:textColor="#android:color/white"
android:textSize="14dp"/>
<ImageView
android:elevation="5dp"
android:scaleType="fitCenter"
android:layout_width="100dp"
android:layout_height="150dp"
android:id="#+id/imageViewPoster"
android:text="Poster"
android:gravity="center_vertical"
android:layout_below="#id/title"
android:layout_marginTop="10dp"
android:layout_marginLeft="5dp"/>
<ImageView
android:id="#+id/imageViewFanart"
android:scaleType="fitCenter"
android:layout_alignBottom="#+id/imageViewPoster"
android:layout_toEndOf="#+id/imageViewPoster"
android:layout_width= "235dp"
android:layout_height="122dp"
android:layout_marginRight="5dp"/>
</RelativeLayout>
Now , somebody in another discussion showed me this as a possible cause : Why RecyclerView.notifyItemChanged() will create a new ViewHolder and use both the old ViewHolder and new one?
So i added the Log items inside the onBind and onCreate methods of the Adapter to see if that could be causing it, and indeed, it`s not just creatind one additional ViewHolders, but 3 of them, and each time i scroll over the newly updated card, it seems to be re-binding, the onBind method seems to be called each time.
Setting the animation to 'null' as per the solution in the thread doesn't seem to have any effect in my case, so maybe that`s not what is causing it.
Any ideas ?
Well, i solved it. It seems to happen only when i'm loading the images via the XML method, if i load them with a 3rd party library like Picasso, the lag seems to dissapear. Something like :
Picasso.with(context).load(MovieDetails.getPoster())
.error(R.drawable.placeholder)
.placeholder(R.drawable.placeholder)
.into(movieViewHolder.imageViewPoster);
inside the onBindViewHolder
and instead of passing around bitmaps, just pass a String for the URL or location of the image. And pass the Context to the Adapter class.
I am creating a simple application to share images in a grid view in Android. I have created an array but when I tried to create a gridview of those images I got an error with my code that I can't resolve.
This is my code :
public class ImageAdapter extends BaseAdapter
{
private Context context;
public ImageAdapter(Context arg2)
{
Object localObject;
this.context = localObject;
}
public int getCount()
{
return MainActivity.this.mThumbIds.length;
}
public Object getItem(int paramInt)
{
return Integer.valueOf(paramInt);
}
public long getItemId(int paramInt)
{
return paramInt;
}
public View getView(int paramInt, View paramView, ViewGroup paramViewGroup)
{
if (paramView != null)
{
MainActivity.this.imageView = ((ImageView)paramView);
}
else
{
MainActivity.this.imageView = new ImageView(this.context);
MainActivity.this.imageView.setLayoutParams(new AbsListView.LayoutParams(90, 90));
MainActivity.this.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
MainActivity.this.imageView.setPadding(5, 5, 5, 5);
}
MainActivity.this.imageView.setImageResource(MainActivity.this.mThumbIds[paramInt].intValue());
return MainActivity.this.imageView;
}
}
}
I got the error here: Type mismatch error: Cannot convert Object to Content The error is pointing on localObject
Object localObject;
Context context;
public ImageAdapter(Context arg2)
{
this.context = arg2;
}
Instead of MainActivity.this.imageView use imageView (this is not an error)
Also use #Override where ever necessary.
Are you not using arg2?
Anyway, try to actually typecast the variable:
this.context = (Context) localObject;
You will use your Base Adapter like this and update your Grid-view like below code..
public class GridViewImageAdapter extends BaseAdapter {
private Activity activity;
private ArrayList<String> filePaths = new ArrayList<String>();
private int imageWidth;
public GridViewImageAdapter(Activity activity,
ArrayList<String> imagePaths, int imageWidth) {
this.activity = activity;
this.filePaths = imagePaths;
this.imageWidth = imageWidth;
}
#Override
public int getCount() {
return this.filePaths.size();
}
#Override
public Object getItem(int position) {
return this.filePaths.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(activity);
} else {
imageView = (ImageView) convertView;
}
// get screen dimensions
Bitmap image = decodeFile(filePaths.get(position), imageWidth,
imageWidth);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(imageWidth,
imageWidth));
imageView.setImageBitmap(image);
return imageView;
}
public static Bitmap decodeFile(String filePath, int WIDTH, int HIGHT) {
try {
File f = new File(filePath);
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
final int REQUIRED_WIDTH = WIDTH;
final int REQUIRED_HIGHT = HIGHT;
int scale = 1;
while (o.outWidth / scale / 2 >= REQUIRED_WIDTH
&& o.outHeight / scale / 2 >= REQUIRED_HIGHT) {
scale *= 2;
}
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
set the adapter value to the GridView,Imagepaths i have stored my images in SD-card so only i give the paths to adapter...All the best
gridView = (GridView) findViewById(R.id.gridView1);
// loading all image paths from SD card
imagePaths = utils.getFilePaths();
// Gridview adapter
adapter = new GridViewImageAdapter(GridViewActivity.this, imagePaths,columnWidth);
// setting grid view adapter
gridView.setAdapter(adapter);
I have a grid View In which I am adding Button dynamically.
I am setting OnTouch listener to grid view.
I want when my finger move on the particular cell then that cell element should get popup
similar way our android keyboard do.
public class MainActivity extends Activity {
private ArrayList<Integer> data;
private GridView gv;
private TextView biggerView = null;
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createData();
gv = (GridView) findViewById(R.id.grid);
gv.setNumColumns(10);
gv.setAdapter(new FilterButtonAdapter(data, this));
gv.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
try {
int position = gv.pointToPosition((int) event.getX(),
(int) event.getY());
View v = (View) gv.getChildAt(position);
if (v != null) {
gv.requestFocus();
gv.setSelection(gv.pointToPosition( (int)
event.getX(), (int) event.getY()));
}
return true;
} catch (Exception e) {
return true;
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
int position = gv.pointToPosition((int) event.getX(),
(int) event.getY());
View v = (View) gv.getChildAt(position);
if (v != null) {
gv.clearFocus();
TextView tv = (TextView) v.findViewById(R.id.texttoadd);
Toast.makeText(MainActivity.this, tv.getText(),
Toast.LENGTH_SHORT).show();
}
return true;
}
return false;
}
});
}
private void createData() {
data = new ArrayList<Integer>();
for (int i = 0; i < 200; i++) {
data.add(i);
}
}
enter code here
i have write this code which is giving me the selected item but when item are more then grid is scrolled and after that the am not getting the item which i am selecting
i have figured out that the x and y position is getting change when grid is scrolled
i may be wrong
please help
I think that suggested in the question way of touch position detection might be not effective, because there's more high-level way to obtain scrolling position.
The main ideas of the implementation are the following:
Use onScrollChanged() to track scroll position at every moment;
Display selection as separate view above GridView;
Track if selected item is visible (using this question);
So, to obtain proper scroll callback, slightly customized GridView is needed:
public class ScrollAwareGridView extends GridView {
/** Callback interface to report immediate scroll changes */
public interface ImmediateScrollListener {
void onImmediateScrollChanged();
}
/** External listener for */
private ImmediateScrollListener mScrollListener = null;
public ScrollAwareGridView(final Context context) {
super(context);
}
public ScrollAwareGridView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public ScrollAwareGridView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (null != mScrollListener) {
mScrollListener.onImmediateScrollChanged();
}
}
/**
* #param listener {#link ImmediateScrollListener}
*/
public void setImmediateScrollListener(final ImmediateScrollListener listener) {
mScrollListener = listener;
}
}
It will be placed in the xml the following way (main.xml):
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.TestApp.ScrollAwareGridView
android:id="#+id/grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3" />
<!-- Selection view -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/selectedImage"
android:visibility="gone" />
</RelativeLayout>
In above xml there's also selection view presented.
Activity will handle selection of items (however, it might be better to keep selection and scroll tracking logic in separate object (grid adapter or specific grid fragment) in order not to keep grid-specific logic in Activity code):
public class MyActivity extends Activity implements ScrollAwareGridView.ImmediateScrollListener, AdapterView.OnItemClickListener {
private static final String TAG = "MyActivity";
/** To start / pause music */
private ImageView mSelectedImage = null;
/** position of selected item in the adapter */
private int mSelectedPosition;
/** Main grid view */
private ScrollAwareGridView mGrid;
/** Adapter for grid view */
private ImageAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Show the layout with the test view
setContentView(R.layout.main);
mSelectedImage = (ImageView) findViewById(R.id.selectedImage);
mGrid = (ScrollAwareGridView) findViewById(R.id.grid);
if (null != mGrid) {
mAdapter = new ImageAdapter(this);
mGrid.setAdapter(mAdapter);
mGrid.setImmediateScrollListener(this);
mGrid.setOnItemClickListener(this);
}
}
#Override
protected void onPause() {
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
mSelectedImage.setImageBitmap(null);
mSelectedImage.setVisibility(View.GONE);
mSelectedPosition = -1;
}
#Override
public void onImmediateScrollChanged() {
if (mSelectedPosition >= 0) {
int firstPosition = mGrid.getFirstVisiblePosition(); // This is the same as child #0
int wantedChild = mSelectedPosition - firstPosition;
// Say, first visible position is 8, you want position 10, wantedChild will now be 2
// So that means your view is child #2 in the ViewGroup:
if (wantedChild < 0 || wantedChild >= mGrid.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
mSelectedImage.setVisibility(View.INVISIBLE);
return;
} else {
mSelectedImage.setVisibility(View.VISIBLE);
}
// Could also check if wantedPosition is between listView.getFirstVisiblePosition() and listView.getLastVisiblePosition() instead.
final View selectedView = mGrid.getChildAt(wantedChild);
if (null != selectedView && mSelectedImage.getVisibility() == View.VISIBLE) {
// Put selected view on new position
final ViewGroup.MarginLayoutParams zoomedImageLayoutParams = (ViewGroup.MarginLayoutParams) mSelectedImage.getLayoutParams();
// 200 is difference between zoomed and not zoomed images dimensions
// TODO: Avoid hardcoded values and use resources
final Integer thumbnailX = mGrid.getLeft() + selectedView.getLeft() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
final Integer thumbnailY = mGrid.getTop() + selectedView.getTop() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
zoomedImageLayoutParams.setMargins(thumbnailX,
thumbnailY,
0,
0);
mSelectedImage.setLayoutParams(zoomedImageLayoutParams);
}
}
}
#Override
public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) {
mSelectedPosition = position;
final Bitmap bm = mAdapter.getImage(position);
// Obtain image from adapter, with check if image presented
if (bm != null) {
final ViewGroup.MarginLayoutParams zoomedImageLayoutParams = (ViewGroup.MarginLayoutParams) mSelectedImage.getLayoutParams();
// 200 is difference between zoomed and not zoomed images dimensions
// TODO: Avoid hardcoded values and use resources
final Integer thumbnailX = mGrid.getLeft() + view.getLeft() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
final Integer thumbnailY = mGrid.getTop() + view.getTop() - (ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION - ImageAdapter.GRID_ITEM_DIMENSION) / 2;
zoomedImageLayoutParams.setMargins(thumbnailX,
thumbnailY,
0,
0);
zoomedImageLayoutParams.height = ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION;
zoomedImageLayoutParams.width = ImageAdapter.HIGHLIGHTED_GRID_ITEM_DIMENSION;
mSelectedImage.setImageBitmap(bm);
mSelectedImage.setScaleType(ImageView.ScaleType.CENTER);
mSelectedImage.setLayoutParams(zoomedImageLayoutParams);
mSelectedImage.setVisibility(View.VISIBLE);
}
}
}
Below is GridViews adapter. However there's nothing specific in it which related to scrolling tracking (most of code I've reused from this answer):
public class ImageAdapter extends BaseAdapter {
private static final String TAG = "ImageAdapter";
/** For creation of child ImageViews */
private Context mContext;
public static final Integer[] IMAGES_RESOURCES = {
R.drawable.image001, R.drawable.image002, R.drawable.image003, R.drawable.image004,
R.drawable.image005, R.drawable.image006, R.drawable.image007, R.drawable.image008,
R.drawable.image009, R.drawable.image010, R.drawable.image011, R.drawable.image012,
R.drawable.image013, R.drawable.image014, R.drawable.image015, R.drawable.image016,
R.drawable.image017, R.drawable.image018, R.drawable.image019, R.drawable.image020,
R.drawable.image021, R.drawable.image022, R.drawable.image023, R.drawable.image024,
R.drawable.image025, R.drawable.image026, R.drawable.image027, R.drawable.image028,
R.drawable.image029, R.drawable.image030, R.drawable.image031, R.drawable.image032,
R.drawable.image033, R.drawable.image034, R.drawable.image035, R.drawable.image036,
R.drawable.image037, R.drawable.image038, R.drawable.image039, R.drawable.image040,
R.drawable.image041, R.drawable.image042, R.drawable.image043, R.drawable.image044,
R.drawable.image045, R.drawable.image046, R.drawable.image047, R.drawable.image048,
R.drawable.image049, R.drawable.image050
};
// TODO: use resources for that sizes, otherwise You'll GET PROBLEMS on other displays!
public final static int GRID_ITEM_DIMENSION = 300;
public final static int HIGHLIGHTED_GRID_ITEM_DIMENSION = 500;
private Bitmap mHolder = null;
private static final int CACHE_SIZE = 50 * 1024 * 1024; // 8 MiB cache
/** Cache to store all decoded images */
private LruCache<Integer, Bitmap> mBitmapsCache = new LruCache<Integer, Bitmap>(CACHE_SIZE) {
#Override
protected int sizeOf(final Integer key, final Bitmap value) {
return value.getByteCount();
}
#Override
protected void entryRemoved(final boolean evicted, final Integer key, final Bitmap oldValue, final Bitmap newValue) {
if (!oldValue.equals(mHolder)) {
oldValue.recycle();
}
}
};
// Constructor
public ImageAdapter(Context c){
mContext = c;
mHolder = BitmapFactory.decodeResource(c.getResources(), R.drawable.ic_launcher, null);
}
#Override
public int getCount() {
return IMAGES_RESOURCES.length;
}
#Override
public Object getItem(int position) {
return IMAGES_RESOURCES[position];
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) {
imageView = new ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new GridView.LayoutParams(GRID_ITEM_DIMENSION, GRID_ITEM_DIMENSION));
} else {
imageView = (ImageView) convertView;
}
final Bitmap itemBitmap = mBitmapsCache.get(IMAGES_RESOURCES[position]);
if (itemBitmap == null || itemBitmap.isRecycled()) {
Log.e(TAG, position + " is missed, launch decode for " + IMAGES_RESOURCES[position]);
imageView.setImageBitmap(mHolder);
mBitmapsCache.put(IMAGES_RESOURCES[position], mHolder);
new BitmapWorkerTask(mBitmapsCache, mContext.getResources(), this).execute(IMAGES_RESOURCES[position]);
} else {
Log.e(TAG, position + " is here for " + IMAGES_RESOURCES[position]);
imageView.setImageBitmap(itemBitmap);
}
return imageView;
}
/**
* Obtains image at position (if there's only holder, then null to be returned)
*
* #param position int position in the adapter
*
* #return {#link Bitmap} image at position or null if image is not loaded yet
*/
public Bitmap getImage(final int position) {
final Bitmap bm = mBitmapsCache.get(IMAGES_RESOURCES[position]);
return ((mHolder.equals(bm) || bm == null) ? null : bm.copy(Bitmap.Config.ARGB_8888, false));
}
/** AsyncTask for decoding images from resources */
static class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private int data = 0;
private final LruCache<Integer, Bitmap> mCache;
private final Resources mRes;
private final BaseAdapter mAdapter;
public BitmapWorkerTask(LruCache<Integer, Bitmap> cache, Resources res, BaseAdapter adapter) {
// nothing to do here
mCache = cache;
mRes = res;
mAdapter = adapter;
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
// Use sizes for selected bitmaps for good up-scaling
return decodeSampledBitmapFromResource(mRes, data, GRID_ITEM_DIMENSION, GRID_ITEM_DIMENSION);
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
mCache.put(data, bitmap);
mAdapter.notifyDataSetChanged();
}
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.outHeight = GRID_ITEM_DIMENSION;
options.outWidth = GRID_ITEM_DIMENSION;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
}