Created
August 29, 2014 20:49
-
-
Save C2H6O/298687262b941b7cbfeb to your computer and use it in GitHub Desktop.
Sony custom listview tutorial
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
/** | |
* 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; | |
} | |
} | |
} |
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
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