HorizontalListView ( http://www.dev-smart.com/archives/34 ) を ListViewに追加しようとしています。ListView はアダプターを使用して、継続的な垂直スクロールのために新しい HorizontalListView 行を動的にアタッチします。
ほとんど機能します: 問題は、HorizontalListView がスクロールされると、ListView が自動的に上下にスクロールし、HorizontalListView がどちらに近いかに応じて、ビューポートの上端または下端に配置されることです。スクロール時に HorizontalListView 行が所定の位置に収まらないようにするにはどうすればよいですか?
「VerticalScrollView」のリファレンスとして HorizontalListView を使用しました。
public class VerticalScrollView extends ListView {
public static interface ScrollListener {
void onScrollEnd();
}
public VerticalScrollView(Context context) {
super(context);
initView();
}
public VerticalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private synchronized void initView() {
mTopViewIndex = -1;
mBottomViewIndex = 0;
mDisplayOffset = 0;
mCurrentY = 0;
mNextY = 0;
mMaxY = Integer.MAX_VALUE;
mScroller = new Scroller(getContext());
setFadingEdgeLength(0);
setItemsCanFocus(false);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDistance = mYDistance = 0f;
mLastX = ev.getX();
mLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
mXDistance += Math.abs(curX - mLastX);
mYDistance += Math.abs(curY - mLastY);
mLastX = curX;
mLastY = curY;
if(mXDistance > mYDistance) {
return false;
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public ListAdapter getAdapter() {
return mAdapter;
}
@Override
public View getSelectedView() {
//TODO: implement
return null;
}
@Override
public void setAdapter(ListAdapter adapter) {
if(mAdapter != null) {
mAdapter.unregisterDataSetObserver(mDataObserver);
}
mAdapter = adapter;
mAdapter.registerDataSetObserver(mDataObserver);
reset();
super.setAdapter(adapter);
}
private synchronized void reset(){
initView();
removeAllViewsInLayout();
requestLayout();
}
@Override
public void setSelection(int position) {
//TODO: implement
}
private void addAndMeasureChild(final View child, int viewPos) {
android.view.ViewGroup.LayoutParams params = child.getLayoutParams();
if(params == null) {
params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
addViewInLayout(child, viewPos, params, true);
child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
}
@Override
protected synchronized void onLayout(
boolean changed,
int left,
int top,
int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if(mAdapter == null){
return;
}
if(mDataChanged){
int oldCurrentY = mCurrentY;
initView();
removeAllViewsInLayout();
mNextY = oldCurrentY;
mDataChanged = false;
}
if(mScroller.computeScrollOffset()){
int scrollY = mScroller.getCurrY();
mNextY = scrollY;
}
if(mNextY <= 0){
mNextY = 0;
mScroller.forceFinished(true);
}
if(mNextY >= mMaxY) {
mNextY = mMaxY;
mScroller.forceFinished(true);
}
int dy = mCurrentY - mNextY;
removeNonVisibleItems(dy);
fillList(dy);
positionItems(dy);
mCurrentY = mNextY;
if(!mScroller.isFinished()){
post(mRequestLayoutRunnable);
}
}
private void fillList(final int dy) {
int edge = 0;
View child = getChildAt(getChildCount()-1);
if(child != null) {
edge = child.getBottom();
}
fillListBottom(edge, dy);
edge = 0;
child = getChildAt(0);
if(child != null) {
edge = child.getTop();
}
fillListTop(edge, dy);
}
private void fillListBottom(int bottomEdge, final int dy) {
while(bottomEdge + dy < getHeight() && mBottomViewIndex < mAdapter.getCount()) {
View child = mAdapter.getView(mBottomViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, -1);
bottomEdge += child.getMeasuredHeight();
if(mBottomViewIndex == mAdapter.getCount()-1) {
mMaxY = mCurrentY + bottomEdge - getHeight();
}
if (mMaxY < 0) {
mMaxY = 0;
}
mBottomViewIndex++;
}
}
private void fillListTop(int topEdge, final int dy) {
while(topEdge + dy > 0 && mTopViewIndex >= 0) {
View child = mAdapter.getView(mTopViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, 0);
topEdge -= child.getMeasuredHeight();
mTopViewIndex--;
mDisplayOffset -= child.getMeasuredHeight();
}
}
private void removeNonVisibleItems(final int dy) {
View child = getChildAt(0);
while(child != null && child.getBottom() + dy <= 0) {
mDisplayOffset += child.getMeasuredHeight();
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mTopViewIndex++;
child = getChildAt(0);
}
child = getChildAt(getChildCount()-1);
while(child != null && child.getLeft() + dy >= getHeight()) {
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mBottomViewIndex--;
child = getChildAt(getChildCount()-1);
}
}
private void positionItems(final int dy) {
if(getChildCount() > 0){
mDisplayOffset += dy;
int top = mDisplayOffset;
for(int i=0;i<getChildCount();i++){
View child = getChildAt(i);
int childHeight = child.getMeasuredHeight();
child.layout(0, top, child.getMeasuredWidth(), top + childHeight);
top += childHeight + child.getPaddingBottom();
}
}
}
public synchronized void scrollTo(int y) {
mScroller.startScroll(0, mNextY, 0, y - mNextY);
requestLayout();
}
public boolean mAlwaysOverrideTouch = true;
protected ListAdapter mAdapter;
private int mTopViewIndex = -1;
private int mBottomViewIndex = 0;
protected int mCurrentY;
protected int mNextY;
private int mMaxY = Integer.MAX_VALUE;
private int mDisplayOffset = 0;
protected Scroller mScroller;
private Queue<View> mRemovedViewQueue = new LinkedList<View>();
private boolean mDataChanged = false;
private float mXDistance;
private float mYDistance;
private float mLastX;
private float mLastY;
private Runnable mRequestLayoutRunnable = new Runnable(){
@Override
public void run() {
requestLayout();
}
};
private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
synchronized(VerticalScrollView.this){
mDataChanged = true;
}
invalidate();
requestLayout();
}
@Override
public void onInvalidated() {
reset();
invalidate();
requestLayout();
}
};
編集: カスタムの VerticalScrollView の代わりにプレーンな ListView を使用することで、これを完全に機能させることができます。ユーザーが下にスクロールすると、ネットワークから ListView に値が入力されます。メモリ不足にならないようにリスト アイテム ビューを管理するにはどうすればよいですか?