RecyclerView and out of memory - android

I'm using RecyclerView to display a few resource-consuming images. My implementation looks like the following:
private class NoteConfigItemAdapter extends RecyclerView.Adapter<NoteConfigItemAdapter.NoteConfigViewHolder> {
public class NoteConfigViewHolder extends RecyclerView.ViewHolder {
private final ToggleButton tbButton;
private final TextView tvDescription;
private int index;
private final View.OnClickListener buttonClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
int oldSelectedItem = selectedItem;
selectedItem = index;
notifyItemChanged(oldSelectedItem);
notifyItemChanged(selectedItem);
}
};
public NoteConfigViewHolder(View itemView) {
super(itemView);
tbButton = (ToggleButton)itemView.findViewById(R.id.notesConfig_tbButton);
tvDescription = (TextView)itemView.findViewById(R.id.notesConfig_tvDescription);
tbButton.setOnClickListener(this.buttonClickListener);
}
public void setData(int descriptionResource, int drawableResource, int index) {
this.index = index;
tvDescription.setText(descriptionResource);
Drawable background = ResourcesCompat.getDrawable(getResources(), drawableResource, null);
tbButton.setBackground(background);
tbButton.setChecked(index == selectedItem);
}
}
private List<NoteConfigItem> configItems;
private int layoutResource;
private int selectedItem = 0;
public NoteConfigItemAdapter(List<NoteConfigItem> configItems, int layoutResource) {
this.configItems = configItems;
this.layoutResource = layoutResource;
}
#Override
public NoteConfigViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater
.from(parent.getContext())
.inflate(layoutResource, parent, false);
return new NoteConfigViewHolder(itemView);
}
#Override
public void onBindViewHolder(NoteConfigViewHolder holder, int position) {
NoteConfigItem item = configItems.get(position);
holder.setData(item.stringResource, item.drawableResource, position);
}
#Override
public int getItemCount() {
return configItems.size();
}
}
Single item's layout contains only ToggleButton and TextView:
I'm dynamically assigning background to the ToggleButton upon binding. Background is a Drawable, which defines two states for the button:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#color/colorPrimary" />
</shape>
</item>
<item>
<inset android:insetTop="#dimen/selectionBorderSize"
android:insetLeft="#dimen/selectionBorderSize"
android:insetRight="#dimen/selectionBorderSize"
android:insetBottom="#dimen/selectionBorderSize">
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff"></solid>
</shape>
</item>
<item>
<bitmap android:src="#drawable/full_range" >
<padding android:bottom="#dimen/selectionBorderSize"
android:top="#dimen/selectionBorderSize"
android:left="#dimen/selectionBorderSize"
android:right="#dimen/selectionBorderSize" />
</bitmap>
</item>
</layer-list>
</inset>
</item>
</layer-list>
</item>
<item android:state_checked="false">
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>
<item>
<inset android:insetTop="#dimen/selectionBorderSize"
android:insetLeft="#dimen/selectionBorderSize"
android:insetRight="#dimen/selectionBorderSize"
android:insetBottom="#dimen/selectionBorderSize">
<layer-list>
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff"></solid>
</shape>
</item>
<item>
<bitmap android:src="#drawable/full_range" >
<padding android:bottom="#dimen/selectionBorderSize"
android:top="#dimen/selectionBorderSize"
android:left="#dimen/selectionBorderSize"
android:right="#dimen/selectionBorderSize" />
</bitmap>
</item>
</layer-list>
</inset>
</item>
</layer-list>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>
</selector>
#drawable/fullRange is a PNG image. Itself it is not so big (~hundred of such images occupy around 400kb), but it is quite big (225px x 450px), so I guess the bitmap object created from this PNG can be memory-costly.
When I run the application, I monitor the memory usage. When I only scroll through RecyclerViews, the chart looks like the following:
But when I start to select items, occupied memory starts to grow until OutOfMemoryException:
What am I doing wrong? Why arent the resources being reused/disposed?

Related

Android Adding Text inside switch toggle. Newer API approach

I want to add text inside switches and expect toggle to completely cover the texts
I was able to achieve this in API 16 with below option
<Switch
android:textOff="off"
android:showText="true"
android:textOn="on"/>
However same switch looks odd in the higher version(i'm testing with API 27) where switch toggle is a round icon behind the text.
Is there a way i can fit the text inside the switch toggle using native library/methods.
I found this code in GitHub. After some addition and modification, I get what I need. This toggle button has YES NO text on it and works like switch button. You can give it a go.
public class ToggleButton extends RelativeLayout implements View.OnClickListener {
FrameLayout layout;
TextView toggleCircle;
View background_oval_off, background_oval_on;
int dimen;
private Boolean _crossfadeRunning = false;
private ObjectAnimator _oaLeft, _oaRight;
public ToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.toggle_button, this, true);
background_oval_off = findViewById(R.id.background_oval_off);
background_oval_on = findViewById(R.id.background_oval_on);
toggleCircle = findViewById(R.id.toggleCircle);
layout = (FrameLayout) findViewById(R.id.layout);
layout.setOnClickListener(this);
//get a pixel size for a particular dimension - will differ by device according to screen density
dimen = getResources().getDimensionPixelSize(R.dimen.toggle_width);
_oaLeft = ObjectAnimator.ofFloat(toggleCircle, "x", dimen / 2, 0).setDuration(250);
_oaRight = ObjectAnimator.ofFloat(toggleCircle, "x", 0, dimen / 2).setDuration(250);
//setState();
}
public ToggleButton(Context context) {
this(context, null);
}
public void setState(String answer) {
Log.d("simul", "ans - " + answer);
if (answer.equals("1")) {
toggleCircle.setTextColor(ContextCompat.getColor(getContext(), R.color.green));
toggleCircle.setX(dimen / 2);
_crossfadeViews(background_oval_off, background_oval_on, 1);
toggleCircle.setText("Yes");
} else {
toggleCircle.setTextColor(ContextCompat.getColor(getContext(), R.color.red));
toggleCircle.setX(0);
_crossfadeViews(background_oval_on, background_oval_off, 1);
toggleCircle.setText("No");
}
}
private void _crossfadeViews(final View begin, View end, int duration) {
_crossfadeRunning = true;
end.setAlpha(0f);
end.setVisibility(View.VISIBLE);
end.animate().alpha(1f).setDuration(duration).setListener(null);
begin.animate().alpha(0f).setDuration(duration).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
begin.setVisibility(View.GONE);
_crossfadeRunning = false;
}
});
}
#Override
public void onClick(View view) {
if (_oaLeft.isRunning() || _oaRight.isRunning() || _crossfadeRunning) return;
if (toggleCircle.getText().equals("Yes")) {
_oaLeft.start();
_crossfadeViews(background_oval_on, background_oval_off, 110);
toggleCircle.setText("No");
toggleCircle.setTextColor(ContextCompat.getColor(getContext(), R.color.red));
mListener.toggleButtonClickListener("No");
} else {
_oaRight.start();
_crossfadeViews(background_oval_off, background_oval_on, 400);
toggleCircle.setText("Yes");
toggleCircle.setTextColor(ContextCompat.getColor(getContext(), R.color.green));
mListener.toggleButtonClickListener("Yes");
}
}
public ToggleButtonListener mListener = null;
public void setToggleButtonClickListener(ToggleButtonListener listener) {
mListener = listener;
}
public interface ToggleButtonListener {
public void toggleButtonClickListener(String content);
}
}
toogle_button_red_oval.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:right="#dimen/toggle_circle_diameter">
<shape android:shape="oval">
<solid android:color="#color/red"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
</item>
<item android:left="#dimen/toggle_circle_radius" android:right="#dimen/toggle_circle_radius">
<shape android:shape="rectangle">
<solid android:color="#color/red"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
</item>
<item android:left="#dimen/toggle_circle_diameter">
<shape android:shape="oval">
<solid android:color="#color/red"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
</item>
</layer-list>
toogle_button_green_oval.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:right="#dimen/toggle_circle_diameter">
<shape android:shape="oval">
<solid android:color="#color/green"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
</item>
<item android:left="#dimen/toggle_circle_radius" android:right="#dimen/toggle_circle_radius">
<shape android:shape="rectangle">
<solid android:color="#color/green"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
</item>
<item android:left="#dimen/toggle_circle_diameter">
<shape android:shape="oval">
<solid android:color="#color/green"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
</item>
</layer-list>
toogle_button_circle.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#color/white"/>
<size
android:height="#dimen/toggle_circle_diameter"
android:width="#dimen/toggle_circle_diameter" />
</shape>
toogle_button.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="#+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:layout_width="#dimen/toggle_width"
android:layout_height="#dimen/toggle_height"
android:id="#+id/background_oval_off"
android:background="#drawable/toggle_button_black_oval" />
<View
android:layout_width="#dimen/toggle_width"
android:layout_height="#dimen/toggle_height"
android:id="#+id/background_oval_on"
android:background="#drawable/toggle_button_green_oval"
android:visibility="gone" />
<TextView
android:paddingTop="10dp"
android:paddingLeft="10dp"
android:layout_width="#dimen/toggle_circle_diameter"
android:layout_height="#dimen/toggle_circle_diameter"
android:layout_gravity="center_vertical"
android:id="#+id/toggleCircle"
android:textSize="#dimen/toggle_text_size"
android:text="No"
android:background="#drawable/toggle_button_circle"/>
</FrameLayout>
</merge>
XML layout code
<package.name.ToggleButton
android:id="#+id/toggleButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</cpackage.name.ToggleButton>
Java Code :
ToggleButton toggleButton = findViewById(R.id.toggleButton);
toggleButton.setToggleButtonClickListener(new ToggleButton.ToggleButtonListener() {
#Override
public void toggleButtonClickListener(String content) {
if (content.equals("Yes")) {
} else {
}
}
});
dimens.xml
<dimen name="toggle_width">100dp</dimen>
<dimen name="toggle_height">50dp</dimen>
<dimen name="toggle_circle_diameter">50dp</dimen>
<dimen name="toggle_circle_radius">25dp</dimen>
Sorry for this long long answer. I had to add all the code here. please let me know if anything missing here. :)

Android selector changes color but not size

I am trying to use a selector to implement a simple dot indicator for a ViewPager.
The dots are supposed to change color and size when the page is changed, but they only change color, while the size stays the same.
The selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<size
android:height="12dp"
android:width="12dp"/>
<solid android:color="#color/highlight"/>
</shape>
</item>
<item>
<shape android:shape="oval">
<size
android:height="6dp"
android:width="6dp"/>
<solid android:color="#color/light_grey"/>
</shape>
</item>
</selector>
which is used in a layout
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="#dimen/margin_2x"
android:src="#drawable/dot_selector">
</ImageView>
which is used in onCreate() to show the initial state:
for (int i = 0; i < mPagerAdapter.getCount(); i++) {
getLayoutInflater().inflate(R.layout.dot_indicator, viewPagerCountDots);
}
viewPagerCountDots.getChildAt(0).setSelected(true);
This looks as expected:
But when I change the selector state when the page is changed like this...
#Override
public void onPageSelected(int position) {
for (int i = 0; i < mPagerAdapter.getCount(); i++) {
viewPagerCountDots.getChildAt(i).setSelected(i == position);
}
}
..it looks like this:
Invalidating the child or the ViewGroup does not do anything. Any clue what is going wrong here?
Update
Calling requestLayout on the child fixes it, as suggested by Bartek Lipinski
#Override
public void onPageSelected(int position) {
for (int i = 0; i < mPagerAdapter.getCount(); i++) {
View dotView = viewPagerCountDots.getChildAt(i);
dotView.setSelected(i == position);
dotView.invalidate();
dotView.requestLayout();
}
}
add requestLayout() calls in your onPageSelected callback:
#Override
public void onPageSelected(int position) {
for (int i = 0; i < mPagerAdapter.getCount(); i++) {
viewPagerCountDots.getChildAt(i).setSelected(i == position);
viewPagerCountDots.getChildAt(i).requestLayout();
}
}
EDIT:
alternatively you can change your drawable to have always the same size, so the setSelected is enough:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<size
android:width="12dp"
android:height="12dp"/>
<solid android:color="#color/highlight"/>
</shape>
</item>
<item>
<layer-list>
<item android:bottom="3dp"
android:left="3dp"
android:right="3dp"
android:top="3dp">
<shape android:shape="oval">
<size
android:width="6dp"
android:height="6dp"/>
<solid android:color="#color/light_grey"/>
</shape>
</item>
</layer-list>
</item>
</selector>
try like this
//if you container is LinearLayout ,change to LinearLayout.params
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
//as you wish
params.width=200;
params.height=200;
#Override
public void onPageSelected(int position) {
for (int i = 0; i < mPagerAdapter.getCount(); i++) {
ImageView dotIv= (ImageView)viewPagerCountDots.getChildAt(i);
dotIv.setImageResource("//normalPic");
if(i==position){
dotIv.setImageResource("//selectPic");
dotIv.setLayoutParams(params);
}
}
}
View sizes are measured once when they are created. Since you set the layout width and height of the ImageView to "wrap_content", non-selected circles will have 6dp size and their size won't change. You can set the layout width and height "12dp".
BTW, I suggest using a library for indicator instead of implementing it. ViewPagerIndicator is a good one

Android XML shape drawable is bogging down my UI

I'm using XML drawables and overlaying them ontop of eachother dynamically using layers. Everytime the user hits a button it overlays another image. The issue is that once I have five or ten layers of this particular XML drawable then the UI wants to just lock up. Here is the drawable:
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:fromDegrees="247.5"
android:toDegrees="247.5"
android:pivotX="50%"
android:pivotY="50%">
<shape android:shape="oval">
<size
android:width="800dp"
android:height="800dp"/>
<stroke
android:dashWidth="235.6dp"
android:dashGap="5000.01dp"
android:width="200dp"
android:color="#c2272d" />
</shape>
</rotate>
</item>
<item android:top="0dp" android:bottom="0dp" android:left="0dp" android:right="0dp">
<rotate
android:fromDegrees="247.5"
android:toDegrees="247.5"
android:pivotX="50%"
android:pivotY="50%">
<shape android:shape="oval">
<size
android:width="800dp"
android:height="800dp"/>
<stroke
android:dashWidth="290.6dp"
android:dashGap="5000.01dp"
android:width="60dp"
android:color="#767171" />
</shape>
</rotate>
</item>
</layer-list>
And here is the code I am using to display and update:
public class ShotMagFragment extends Fragment implements DataDelegate, View.OnClickListener{
private ImageView mainImage;
private ArrayList<Drawable> layers = new ArrayList<>();
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_shot_mag, container, false);
View simulateBtn = view.findViewById(R.id.simulateBtn);
simulateBtn.setOnClickListener(this);
mainImage = (ImageView)view.findViewById(R.id.mainImage);
return view;
}
public void displayNewImage() {
Random r = new Random();
int number = 1 + (20 - 20) * r.nextInt();
Drawable d = getResources().getDrawable(getResources().getIdentifier("mag_shade" + number, "drawable", getContext().getPackageName()));
layers.add(d);
final LayerDrawable layerDrawable = new LayerDrawable(layers.toArray(new Drawable[layers.size()]));
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mainImage.setImageDrawable(layerDrawable);
}
});
}
#Override
public void onClick(View v) {
displayNewImage();
}
}
But when I use a different XML drawable I don't get the same UI lock up problem:
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="130dp" android:bottom="130dp" android:left="130dp" android:right="130dp">
<rotate
android:fromDegrees="247.5"
android:toDegrees="247.5"
android:pivotX="50%"
android:pivotY="50%">
<shape android:shape="oval">
<size
android:width="540dp"
android:height="540dp"/>
<stroke
android:dashWidth="212.0dp"
android:dashGap="5000.01dp"
android:width="4dp"
android:color="#fff" />
</shape>
</rotate>
</item>
</layer-list>
EDIT
I also get a weird error icon next to my reference to my drawable. When I click on it, it just takes me to the drawable. I have no idea if that is related to my problem, if it is a real error, or if it is just a notice.

Circle button with shadow

I make a circle button with states and I want to add shadow to the button like the default buttons how I can do this? Here is my code:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="#color/yellow_pressed" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="oval">
<solid android:color="#color/yellow_pressed" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="#color/yellow_default" />
<stroke android:width="1dp" android:color="#fff" />
</shape>
</item>
</selector>
Starting from Lollipop, you can use:
public static void setOvalElevationToView(final View view) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP)
view.setOutlineProvider(new ViewOutlineProvider() {
#TargetApi(VERSION_CODES.LOLLIPOP)
#Override
public void getOutline(View view, Outline outline) {
final int size = view.getWidth();
outline.setOval(0, 0, size, size);
}
});
}
You might also need to disable clipping on the parent of the view, so that the shadows won't be clipped

Gridview selected item keep highlighted and confirm image

I am developing an android app with GridView. The gridView contains buttons. I have customized my gridView with button adapter.
The question is: I need to keep the selected item highlighted with borders. Now the selection is disappearing after releasing the press. I am not an expert in android. So the next step after pressing the button is to display an image of cloud shape that says "confirm".
This is what I exactly needed.
Create a folder named drawable in your res folder. Now create a xml file in drawable folder, name it anything (in small laters) and put this code in the file.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:state_pressed="true"><shape>
<!-- <solid android:color="#CCCCCC"/> -->
<gradient android:endColor="#67A7F8" android:startColor="#1067C8" />
<stroke android:width="1dp" android:color="#000000" />
<corners android:radius="8dp" />
</shape></item>
<item android:state_focused="false" android:state_pressed="true"><shape>
<!-- <solid android:color="#07B107"/> -->
<gradient android:endColor="#67A7F8" android:startColor="#1067C8" />
<stroke android:width="1dp" android:color="#000000" />
<corners android:radius="8dp" />
</shape></item>
<item android:state_focused="true" android:state_pressed="false"><shape>
<solid android:color="#FFFFFF" />
<stroke android:width="1dp" android:color="#0055FF" />
<corners android:radius="8dp" />
</shape></item>
<item android:state_focused="false" android:state_pressed="false"><shape>
<gradient android:angle="270" android:centerColor="#FFFFFF" android:endColor="#FFFFFF" android:startColor="#F2F2F2" />
<stroke android:width="0.8dp" android:color="#000000" />
<corners android:radius="12dp" />
</shape></item>
<item android:state_enabled="true"><shape>
<padding android:bottom="4dp" android:left="5dp" android:right="4dp" android:top="4dp" />
</shape></item>
</selector>
now in your GridView code set android:drawSelectorOnTop="true" and android:listSelector properties and select the drawable you have just created. It will resolve your problem.
Your grid view code will look something like this :
<GridView
android:id="#+id/lstFrames_available_frames"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:animateLayoutChanges="true"
android:columnWidth="100dp"
android:gravity="center"
android:horizontalSpacing="3dp"
android:listSelector="#drawable/round_buttons"
android:numColumns="auto_fit"
android:drawSelectorOnTop="true"
android:stretchMode="columnWidth"
android:verticalSpacing="3dp" >
</GridView>
UPDATE
You can use something like this in your custom adapter to achieve what you want :
public class AlbumCoverAdapter extends BaseAdapter {
private Activity activity;
private static LayoutInflater inflater = null;
private int mSelected;
public AlbumCoverAdapter(Activity a) {
activity = a;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return 50;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public static class ViewHolder {
public TextView txtCaption;
public ImageView imgImage;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
ViewHolder holder;
if (convertView == null) {
vi = inflater.inflate(R.layout.grid_adapter, null);
holder = new ViewHolder();
holder.txtCaption = (TextView)vi.findViewById(R.id.txtGridText);
holder.txtCaption.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
mSelected = (Integer)arg0.getTag();
notifyDataSetChanged();
}
});
vi.setTag(holder);
} else
holder = (ViewHolder)vi.getTag();
try {
holder.txtCaption.setTag(position);
if (position == mSelected) {
holder.txtCaption.setBackgroundResource(R.drawable.round_corner_background);
} else {
holder.txtCaption.setBackgroundDrawable(null);
}
holder.txtCaption.setText("Item: " + position);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return vi;
}
}

Categories

Resources