Skip to content

Instantly share code, notes, and snippets.

@C2H6O
Created August 29, 2014 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save C2H6O/298687262b941b7cbfeb to your computer and use it in GitHub Desktop.
Save C2H6O/298687262b941b7cbfeb to your computer and use it in GitHub Desktop.
Sony custom listview tutorial
/**
* Test activity to display the list view
*/
public class MainActivity extends Activity {
/** The list view */
private MyListView mListView;
/**
* Small class that represents a contact
*/
private static class Contact {
/** Name of the contact */
String mName;
/** Phone number of the contact */
String mNumber;
/**
* Constructor
*
* @param name The name
* @param number The number
*/
public Contact(final String name, final String number) {
mName = name;
mNumber = number;
}
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ArrayList<Contact> contacts = createContactList(20);
final MyAdapter adapter = new MyAdapter(this, contacts);
mListView = (MyListView)findViewById(R.id.my_list);
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
final String message = "OnClick: " + contacts.get(position).mName;
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
public boolean onItemLongClick(final AdapterView<?> parent, final View view,
final int position, final long id) {
final String message = "OnLongClick: " + contacts.get(position).mName;
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
return true;
}
});
}
/**
* Creates a list of fake contacts
*
* @param size How many contacts to create
* @return A list of fake contacts
*/
private ArrayList<Contact> createContactList(final int size) {
final ArrayList<Contact> contacts = new ArrayList<Contact>();
for (int i = 0; i < size; i++) {
contacts.add(new Contact("Contact Number " + i, "+46(0)"
+ (int)(1000000 + 9000000 * Math.random())));
}
return contacts;
}
/**
* Adapter class to use for the list
*/
private static class MyAdapter extends ArrayAdapter<Contact> {
/** Re-usable contact image drawable */
private final Drawable contactImage;
/**
* Constructor
*
* @param context The context
* @param contacts The list of contacts
*/
public MyAdapter(final Context context, final ArrayList<Contact> contacts) {
super(context, 0, contacts);
contactImage = context.getResources().getDrawable(R.drawable.contact_image);
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
View view = convertView;
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
Holder holder = new Holder();
holder.name = (TextView)view.findViewById(R.id.contact_name);
holder.number = (TextView)view.findViewById(R.id.contact_number);
holder.photo = (ImageView)view.findViewById(R.id.contact_photo);
view.setTag(holder);
}
Holder holder = (Holder) view.getTag();
if (position == 14) {
holder.name.setText("This is a long text that will make this box big. "
+ "Really big. Bigger than all the other boxes. Biggest of them all.");
} else {
holder.name.setText(getItem(position).mName);
Log.d("NAME", getItem(position).mName + " position: " + position);
}
holder.number.setText(getItem(position).mNumber);
holder.photo.setImageDrawable(contactImage);
return view;
}
private static class Holder {
TextView name;
TextView number;
ImageView photo;
}
}
}
public class MyListView extends AdapterView {
public static final String TAG = "MyListView";
private Adapter mAdapter;
int mTouchStartY;
int mListTopStart;
int mListTop;
int mFirstItemPosition;
int mLastItemPosition;
int mListTopOffset;
// private LinkedList<View> mCachedViews = new LinkedList<View>();
private Stack<View> mCachedViews = new Stack<View>();
/** Children added with this layout mode will be added below the last child */
private static final int LAYOUT_MODE_BELOW = 0;
/** Children added with this layout mode will be added above the first child */
private static final int LAYOUT_MODE_ABOVE = 1;
/** User is not touching the list */
private static final int TOUCH_STATE_RESTING = 0;
/** User is touching the list and right now it's still a "click" */
private static final int TOUCH_STATE_CLICK = 1;
/** User is scrolling the list */
private static final int TOUCH_STATE_SCROLL = 2;
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void fillList(int offset) {
int botEdge = getChildAt(getChildCount() - 1).getBottom();
fillListDown(botEdge, offset);
int topEdge = getChildAt(0).getTop();
fillListUp(topEdge, offset);
}
private View getCachedView() {
if (mCachedViews.size() != 0) {
return mCachedViews.pop();
}
return null;
}
private void fillListDown(int bottomEdge, int offset) {
Log.d(TAG, "fillListDown");
while (bottomEdge + offset < getHeight() && mLastItemPosition != mAdapter.getCount() - 1) {
Log.d(TAG, "While loop: bottomEdge: " + bottomEdge + " mLastItemPosition: " + mLastItemPosition + " offset: " + offset);
mLastItemPosition++;
View child = mAdapter.getView(mLastItemPosition, getCachedView(), this);
addAndMeasureChild(child, LAYOUT_MODE_BELOW);
bottomEdge += child.getMeasuredHeight();
}
}
private void fillListUp(int topEdge, int offset) {
Log.d(TAG, "fillListUp");
while (topEdge + offset > 0 && mFirstItemPosition != 0) {
Log.d(TAG, "While loop: top edge: " + topEdge + " mFirstItemPosition: " + mFirstItemPosition + " offset: " + offset);
mFirstItemPosition--;
View child = mAdapter.getView(mFirstItemPosition, getCachedView(), this);
addAndMeasureChild(child, LAYOUT_MODE_ABOVE);
topEdge -= child.getMeasuredHeight();
mListTopOffset -= child.getMeasuredHeight();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mAdapter == null)
return;
if (getChildCount() == 0) {
mFirstItemPosition = 0;
mLastItemPosition = -1;
fillListDown(0, 0);
} else {
int offset = mListTop + mListTopOffset - getChildAt(0).getTop();
// Remove invisible views if any
removeInvisibleViews(offset);
fillList(offset);
}
Log.d(TAG, "Child count: " + getChildCount());
invalidate();
positionItems();
}
private void removeInvisibleViews(int offset) {
int childCount = getChildCount();
View firstChild = getChildAt(0);
while (firstChild.getBottom() + offset < 0 && childCount > 1) {
mFirstItemPosition++;
mListTopOffset += firstChild.getMeasuredHeight();
removeViewInLayout(firstChild);
mCachedViews.push(firstChild);
childCount--;
firstChild = getChildAt(0);
}
View lastChild = getChildAt(getChildCount() - 1);
while (lastChild.getTop() + offset > getHeight() && childCount > 1) {
mLastItemPosition--;
removeViewInLayout(lastChild);
mCachedViews.push(lastChild);
childCount--;
lastChild = getChildAt(getChildCount() - 1);
}
}
/**
* Adds a view as a child view and takes care of measuring it
*
* @param child The view to add
* @param layoutMode Either LAYOUT_MODE_ABOVE or LAYOUT_MODE_BELOW
*/
private void addAndMeasureChild(final View child, final int layoutMode) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
final int index = layoutMode == LAYOUT_MODE_ABOVE ? 0 : -1;
addViewInLayout(child, index, params, true);
final int itemWidth = getWidth();
child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
}
private void positionItems() {
int top = mListTop + mListTopOffset;
Log.d(TAG, "mListTop: " + mListTop + " mListTopOffset: " + mListTopOffset);
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int left = (getWidth() - width) / 2;
child.layout(left, top, left + width, top + height);
top += height;
}
}
private void startTouch(MotionEvent event) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getChildCount() == 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(event);
mTouchStartY = (int) event.getY();
mListTopStart = getChildAt(0).getTop() - mListTopOffset;
break;
case MotionEvent.ACTION_MOVE:
int scrolledDistance = (int) event.getY() - mTouchStartY;
mListTop = mListTopStart + scrolledDistance;
requestLayout();
break;
default:
break;
}
return true;
}
@Override
public Adapter getAdapter() {
return mAdapter;
}
@Override
public void setAdapter(Adapter adapter) {
this.mAdapter = adapter;
removeAllViewsInLayout();
requestLayout();
}
@Override
public View getSelectedView() {
// Log.d(TAG, "getSelectedView");
return null;
}
@Override
public void setSelection(int position) {
Log.d(TAG, "setSelection");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment