Created
November 13, 2015 09:16
-
-
Save urandom/1061af945e8ee91577b3 to your computer and use it in GitHub Desktop.
RecyclerView Grid adapter with header support and proper merging of items
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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