Skip to content

Instantly share code, notes, and snippets.

@urandom
Created November 13, 2015 09:16
Show Gist options
  • Save urandom/1061af945e8ee91577b3 to your computer and use it in GitHub Desktop.
Save urandom/1061af945e8ee91577b3 to your computer and use it in GitHub Desktop.
RecyclerView Grid adapter with header support and proper merging of items
package com.example.test.adapter;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.transitionseverywhere.TransitionManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.devolo.test.R;
import de.devolo.test.core.Device;
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
private List<DeviceWrapper> wrappers = new ArrayList<>();
private boolean displayHeaders;
private static final int VIEW_TYPE_HEADER = 0;
private static final int VIEW_TYPE_CONTENT = 1;
@Inject public Adapter() {}
synchronized public void setDevices(Device... devices) {
Set<Device> deviceSet = new HashSet<>(Arrays.asList(devices));
Arrays.sort(devices, (lhs, rhs) -> {
String lg = lhs.getGroup();
String rg = rhs.getGroup();
if (lg == null) {
if (rg == null) {
return 0;
} else {
return -1;
}
} else {
if (rg == null) {
return 1;
}
int gc = lg.compareTo(rg);
if (gc == 0) {
return lhs.getName().compareTo(rhs.getName());
} else {
return gc;
}
}
});
boolean[] removal = new boolean[wrappers.size()];
int sectionIndex = -1;
boolean emptySection = true;
for (int i = 0; i < wrappers.size(); ++i) {
DeviceWrapper w = wrappers.get(i);
if (!w.isHeader) {
if (deviceSet.contains(w.device)) {
emptySection = false;
} else {
removal[i] = true;
}
}
if (w.isHeader || i == wrappers.size() - 1) {
if (emptySection && sectionIndex != -1) {
removal[sectionIndex] = true;
}
sectionIndex = i;
emptySection = true;
}
}
for (int i = removal.length - 1, removalEnd = -1; i > -1; --i) {
if (removal[i]) {
wrappers.remove(i);
if (removalEnd == -1) {
removalEnd = i;
}
}
if (removalEnd != -1 && (!removal[i] || i == 0)) {
int start = removal[i] ? i : i + 1;
int count = removal[i] ? removalEnd - i + 1 : removalEnd - i;
notifyItemRangeRemoved(start, count);
removalEnd = -1;
}
}
Set<String> groups = new HashSet<>();
for (int i = 0; i < wrappers.size(); ++i) {
DeviceWrapper w = wrappers.get(i);
if (w.isHeader) {
groups.add(w.group);
}
}
for (int i = 0, j = 0, inserts = 0, insertStart = -1; i < devices.length; ++i, ++j) {
Device d = devices[i];
DeviceWrapper w = j == wrappers.size() ? new DeviceWrapper() : wrappers.get(j);
if (w.isHeader && w.group.equals(d.getGroup())) {
w = wrappers.get(++j);
}
if (!d.equals(w.device)) {
// Device doesn't exist
if (insertStart == -1) {
insertStart = j;
}
if (d.getGroup() != null && displayHeaders) {
if (!groups.contains(d.getGroup())) {
wrappers.add(j, new DeviceWrapper(d.getGroup()));
groups.add(d.getGroup());
++j;
++inserts;
}
}
wrappers.add(j, new DeviceWrapper(d));
++inserts;
} else if (d.changed(w.device)) {
notifyItemChanged(j);
}
if (w.isHeader || d.equals(w.device) || i == devices.length - 1) {
if (insertStart != -1) {
notifyItemRangeInserted(insertStart, inserts);
inserts = 0;
insertStart = -1;
}
}
}
}
// Must be called before any devices have been set
public void setHeaders(boolean headers) {
displayHeaders = headers;
}
public boolean isHeader(int position) {
return getItemViewType(position) == VIEW_TYPE_HEADER;
}
@Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
int layout = viewType == VIEW_TYPE_HEADER ?
R.layout.header :
R.layout.device;
View v = LayoutInflater.from(parent.getContext())
.inflate(layout, parent, false);
return new ViewHolder(v);
}
@Override public void onBindViewHolder(ViewHolder holder, int position) {
DeviceWrapper w = wrappers.get(position);
switch (getItemViewType(position)) {
case VIEW_TYPE_HEADER:
holder.name.setText(w.group);
break;
case VIEW_TYPE_CONTENT:
holder.name.setText(w.device.getName());
break;
}
}
@Override public int getItemCount() {
return wrappers.size();
}
@Override public int getItemViewType(int position) {
return wrappers.get(position).isHeader ?
VIEW_TYPE_HEADER : VIEW_TYPE_CONTENT;
}
public class ViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.name) TextView name;
public ViewHolder(View v) {
super(v);
ButterKnife.bind(this, v);
}
}
private static class DeviceWrapper {
Device device;
String group;
boolean isHeader;
DeviceWrapper() {}
DeviceWrapper(Device d) {
device = d;
}
DeviceWrapper(String g) {
group = g;
isHeader = true;
}
@Override public String toString() {
return isHeader ? "Group: " + group : "Dev: " + device;
}
}
}
package com.example.test.core;
import android.os.Parcel;
import android.os.Parcelable;
public class Device implements Parcelable {
private String name;
private String type;
private String group;
private String mac;
public Device(String name, String type) {
this.name = name;
this.type = type;
}
public Device(String name, String type, String group) {
this(name, type);
this.group = group;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getMac() {
return mac;
}
public void setMac(String mac) {
this.mac = mac;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Device device = (Device) o;
if (!name.equals(device.name)) return false;
if (!type.equals(device.type)) return false;
if (group != null ? !group.equals(device.group) : device.group != null) return false;
return !(mac != null ? !mac.equals(device.mac) : device.mac != null);
}
@Override public int hashCode() {
int result = name.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + (group != null ? group.hashCode() : 0);
result = 31 * result + (mac != null ? mac.hashCode() : 0);
return result;
}
public boolean changed(Device o) {
if (!this.equals(o)) {
return false;
}
return false;
}
@Override public String toString() {
return name + " (" + type + ")" + (group == null ? "" : " [" + group + "]");
}
@Override public int describeContents() {
return 0;
}
@Override public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeString(this.type);
dest.writeString(this.group);
dest.writeString(this.mac);
}
private Device(Parcel in) {
this.name = in.readString();
this.type = in.readString();
this.group = in.readString();
this.mac = in.readString();
}
public static final Parcelable.Creator<Device> CREATOR = new Parcelable.Creator<Device>() {
public Device createFromParcel(Parcel source) {
return new Device(source);
}
public Device[] newArray(int size) {
return new Device[size];
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment