我有这个水平的列表视图。但很难水平滚动。我该怎么办?这是水平listview的代码?我应该更改任何内容以使列表视图更流畅吗?
public class HorizontalListView extends AdapterView<ListAdapter> {
private static final int INSERT_AT_END_OF_LIST = -1;
private static final int INSERT_AT_START_OF_LIST = 0;
private static final float FLING_DEFAULT_ABSORB_VELOCITY = 30f;
/** The friction amount to use for the fling tracker */
private static final float FLING_FRICTION = 1.1f;
private static final String BUNDLE_ID_CURRENT_X = "BUNDLE_ID_CURRENT_X";
private static final String BUNDLE_ID_PARENT_STATE = "BUNDLE_ID_PARENT_STATE";
protected Scroller mFlingTracker = new Scroller(getContext());
/** Gesture listener to receive callbacks when gestures are detected */
private final GestureListener mGestureListener = new GestureListener();
private GestureDetector mGestureDetector;
private int mDisplayOffset;
protected ListAdapter mAdapter;
private List<Queue<View>> mRemovedViewsCache = new ArrayList<Queue<View>>();
/** Flag used to mark when the adapters data has changed, so the view can be relaid out */
private boolean mDataChanged = false;
/** Temporary rectangle to be used for measurements */
private Rect mRect = new Rect();
/** Tracks the currently touched view, used to delegate touches to the view being touched */
private View mViewBeingTouched = null;
/** The width of the divider that will be used between list items */
private int mDividerWidth = 0;
/** The drawable that will be used as the list divider */
private Drawable mDivider = null;
/** The x position of the currently rendered view */
protected int mCurrentX;
/** The x position of the next to be rendered view */
protected int mNextX;
/** Used to hold the scroll position to restore to post rotate */
private Integer mRestoreX = null;
/** Tracks the maximum possible X position, stays at max value until last item is laid out and it can be determined */
private int mMaxX = Integer.MAX_VALUE;
/** The adapter index of the leftmost view currently visible */
private int mLeftViewAdapterIndex;
/** The adapter index of the rightmost view currently visible */
private int mRightViewAdapterIndex;
/** This tracks the currently selected accessibility item */
private int mCurrentlySelectedAdapterIndex;
/**
* Callback interface to notify listener that the user has scrolled this view to the point that it is low on data.
*/
private RunningOutOfDataListener mRunningOutOfDataListener = null;
/**
* This tracks the user value set of how many items from the end will be considered running out of data.
*/
private int mRunningOutOfDataThreshold = 0;
/**
* Tracks if we have told the listener that we are running low on data. We only want to tell them once.
*/
private boolean mHasNotifiedRunningLowOnData = false;
/**
* Callback interface to be invoked when the scroll state has changed.
*/
private OnScrollStateChangedListener mOnScrollStateChangedListener = null;
/**
* Represents the current scroll state of this view. Needed so we can detect when the state changes so scroll listener can be notified.
*/
private OnScrollStateChangedListener.ScrollState mCurrentScrollState = OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE;
/**
* Tracks the state of the left edge glow.
*/
private EdgeEffectCompat mEdgeGlowLeft;
/**
* Tracks the state of the right edge glow.
*/
private EdgeEffectCompat mEdgeGlowRight;
/** The height measure spec for this view, used to help size children views */
private int mHeightMeasureSpec;
/** Used to track if a view touch should be blocked because it stopped a fling */
private boolean mBlockTouchAction = false;
/** Used to track if the parent vertically scrollable view has been told to DisallowInterceptTouchEvent */
private boolean mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = false;
/**
* The listener that receives notifications when this view is clicked.
*/
private OnClickListener mOnClickListener;
public HorizontalListView(Context context, AttributeSet attrs) {
super(context, attrs);
mEdgeGlowLeft = new EdgeEffectCompat(context);
mEdgeGlowRight = new EdgeEffectCompat(context);
mGestureDetector = new GestureDetector(context, mGestureListener);
bindGestureDetector();
initView();
retrieveXmlConfiguration(context, attrs);
setWillNotDraw(false);
// If the OS version is high enough then set the friction on the fling tracker */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
HoneycombPlus.setFriction(mFlingTracker, FLING_FRICTION);
}
}
/** Registers the gesture detector to receive gesture notifications for this view */
private void bindGestureDetector() {
// Generic touch listener that can be applied to any view that needs to process gestures
final View.OnTouchListener gestureListenerHandler = new View.OnTouchListener() {
@Override
public boolean onTouch(final View v, final MotionEvent event) {
// Delegate the touch event to our gesture detector
return mGestureDetector.onTouchEvent(event);
}
};
setOnTouchListener(gestureListenerHandler);
}
private void requestParentListViewToNotInterceptTouchEvents(Boolean disallowIntercept) {
// Prevent calling this more than once needlessly
if (mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent != disallowIntercept) {
View view = this;
while (view.getParent() instanceof View) {
// If the parent is a ListView or ScrollView then disallow intercepting of touch events
if (view.getParent() instanceof ListView || view.getParent() instanceof ScrollView) {
view.getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
mIsParentVerticiallyScrollableViewDisallowingInterceptTouchEvent = disallowIntercept;
return;
}
view = (View) view.getParent();
}
}
}
private void retrieveXmlConfiguration(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalListView);
// Get the provided drawable from the XML
final Drawable d = a.getDrawable(R.styleable.HorizontalListView_android_divider);
if (d != null) {
// If a drawable is provided to use as the divider then use its intrinsic width for the divider width
setDivider(d);
}
// If a width is explicitly specified then use that width
final int dividerWidth = a.getDimensionPixelSize(R.styleable.HorizontalListView_dividerWidth, 0);
if (dividerWidth != 0) {
setDividerWidth(dividerWidth);
}
a.recycle();
}
}
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// Add the parent state to the bundle
bundle.putParcelable(BUNDLE_ID_PARENT_STATE, super.onSaveInstanceState());
// Add our state to the bundle
bundle.putInt(BUNDLE_ID_CURRENT_X, mCurrentX);
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
// Restore our state from the bundle
mRestoreX = Integer.valueOf((bundle.getInt(BUNDLE_ID_CURRENT_X)));
// Restore out parent's state from the bundle
super.onRestoreInstanceState(bundle.getParcelable(BUNDLE_ID_PARENT_STATE));
}
}
public void setDivider(Drawable divider) {
mDivider = divider;
if (divider != null) {
setDividerWidth(divider.getIntrinsicWidth());
} else {
setDividerWidth(0);
}
}
/**
* Sets the width of the divider that will be drawn between each item in the list. Calling
* this will override the intrinsic width as set by {@link #setDivider(Drawable)}
*
* @param width The width of the divider in pixels.
*/
public void setDividerWidth(int width) {
mDividerWidth = width;
// Force the view to rerender itself
requestLayout();
invalidate();
}
private void initView() {
mLeftViewAdapterIndex = -1;
mRightViewAdapterIndex = -1;
mDisplayOffset = 0;
mCurrentX = 0;
mNextX = 0;
mMaxX = Integer.MAX_VALUE;
setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
}
/** Will re-initialize the HorizontalListView to remove all child views rendered and reset to initial configuration. */
private void reset() {
initView();
removeAllViewsInLayout();
requestLayout();
}
/** DataSetObserver used to capture adapter data change events */
private DataSetObserver mAdapterDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
mDataChanged = true;
// Clear so we can notify again as we run out of data
mHasNotifiedRunningLowOnData = false;
unpressTouchedChild();
// Invalidate and request layout to force this view to completely redraw itself
invalidate();
requestLayout();
}
@Override
public void onInvalidated() {
// Clear so we can notify again as we run out of data
mHasNotifiedRunningLowOnData = false;
unpressTouchedChild();
reset();
// Invalidate and request layout to force this view to completely redraw itself
invalidate();
requestLayout();
}
};
@Override
public void setSelection(int position) {
mCurrentlySelectedAdapterIndex = position;
}
@Override
public View getSelectedView() {
return getChild(mCurrentlySelectedAdapterIndex);
}
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mAdapterDataObserver);
}
if (adapter != null) {
// Clear so we can notify again as we run out of data
mHasNotifiedRunningLowOnData = false;
mAdapter = adapter;
mAdapter.registerDataSetObserver(mAdapterDataObserver);
}
initializeRecycledViewCache(mAdapter.getViewTypeCount());
reset();
}
@Override
public ListAdapter getAdapter() {
return mAdapter;
}
/**
* Will create and initialize a cache for the given number of different types of views.
*
* @param viewTypeCount - The total number of different views supported
*/
private void initializeRecycledViewCache(int viewTypeCount) {
// The cache is created such that the response from mAdapter.getItemViewType is the array index to the correct cache for that item.
mRemovedViewsCache.clear();
for (int i = 0; i < viewTypeCount; i++) {
mRemovedViewsCache.add(new LinkedList<View>());
}
}
/**
* Returns a recycled view from the cache that can be reused, or null if one is not available.
*
* @param adapterIndex
* @return
*/
private View getRecycledView(int adapterIndex) {
int itemViewType = mAdapter.getItemViewType(adapterIndex);
if (isItemViewTypeValid(itemViewType)) {
return mRemovedViewsCache.get(itemViewType).poll();
}
return null;
}
private void recycleView(int adapterIndex, View view) {
// There is one Queue of views for each different type of view.
// Just add the view to the pile of other views of the same type.
// The order they are added and removed does not matter.
int itemViewType = mAdapter.getItemViewType(adapterIndex);
if (isItemViewTypeValid(itemViewType)) {
mRemovedViewsCache.get(itemViewType).offer(view);
}
}
private boolean isItemViewTypeValid(int itemViewType) {
return itemViewType < mRemovedViewsCache.size();
}
/** Adds a child to this viewgroup and measures it so it renders the correct size */
private void addAndMeasureChild(final View child, int viewPos) {
LayoutParams params = getLayoutParams(child);
addViewInLayout(child, viewPos, params, true);
measureChild(child);
}
/**
* Measure the provided child.
*
* @param child The child.
*/
private void measureChild(View child) {
ViewGroup.LayoutParams childLayoutParams = getLayoutParams(child);
int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, getPaddingTop() + getPaddingBottom(), childLayoutParams.height);
int childWidthSpec;
if (childLayoutParams.width > 0) {
childWidthSpec = MeasureSpec.makeMeasureSpec(childLayoutParams.width, MeasureSpec.EXACTLY);
} else {
childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
/** Gets a child's layout parameters, defaults if not available. */
private ViewGroup.LayoutParams getLayoutParams(View child) {
ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
if (layoutParams == null) {
// Since this is a horizontal list view default to matching the parents height, and wrapping the width
layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
return layoutParams;
}
@SuppressLint("WrongCall")
@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;
}
// Force the OS to redraw this view
invalidate();
// If the data changed then reset everything and render from scratch at the same offset as last time
if (mDataChanged) {
int oldCurrentX = mCurrentX;
initView();
removeAllViewsInLayout();
mNextX = oldCurrentX;
mDataChanged = false;
}
// If restoring from a rotation
if (mRestoreX != null) {
mNextX = mRestoreX;
mRestoreX = null;
}
// If in a fling
if (mFlingTracker.computeScrollOffset()) {
// Compute the next position
mNextX = mFlingTracker.getCurrX();
}
// Prevent scrolling past 0 so you can't scroll past the end of the list to the left
if (mNextX < 0) {
mNextX = 0;
// Show an edge effect absorbing the current velocity
if (mEdgeGlowLeft.isFinished()) {
mEdgeGlowLeft.onAbsorb((int) determineFlingAbsorbVelocity());
}
mFlingTracker.forceFinished(true);
setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
} else if (mNextX > mMaxX) {
// Clip the maximum scroll position at mMaxX so you can't scroll past the end of the list to the right
mNextX = mMaxX;
// Show an edge effect absorbing the current velocity
if (mEdgeGlowRight.isFinished()) {
mEdgeGlowRight.onAbsorb((int) determineFlingAbsorbVelocity());
}
mFlingTracker.forceFinished(true);
setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
}
// Calculate our delta from the last time the view was drawn
int dx = mCurrentX - mNextX;
removeNonVisibleChildren(dx);
fillList(dx);
positionChildren(dx);
// Since the view has now been drawn, update our current position
mCurrentX = mNextX;
// If we have scrolled enough to lay out all views, then determine the maximum scroll position now
if (determineMaxX()) {
// Redo the layout pass since we now know the maximum scroll position
onLayout(changed, left, top, right, bottom);
return;
}
// If the fling has finished
if (mFlingTracker.isFinished()) {
// If the fling just ended
if (mCurrentScrollState == OnScrollStateChangedListener.ScrollState.SCROLL_STATE_FLING) {
setCurrentScrollState(OnScrollStateChangedListener.ScrollState.SCROLL_STATE_IDLE);
}
} else {
// Still in a fling so schedule the next frame
ViewCompat.postOnAnimation(this, mDelayedLayout);
}
}
@Override
protected float getLeftFadingEdgeStrength() {
int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
// If completely at the edge then disable the fading edge
if (mCurrentX == 0) {
return 0;
} else if (mCurrentX < horizontalFadingEdgeLength) {
// We are very close to the edge, so enable the fading edge proportional to the distance from the edge, and the width of the edge effect
return (float) mCurrentX / horizontalFadingEdgeLength;
} else {
// The current x position is more then the width of the fading edge so enable it fully.
return 1;
}
}
@Override
protected float getRightFadingEdgeStrength() {
int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength();
// If completely at the edge then disable the fading edge
if (mCurrentX == mMaxX) {
return 0;
} else if ((mMaxX - mCurrentX) < horizontalFadingEdgeLength) {
// We are very close to the edge, so enable the fading edge proportional to the distance from the ednge, and the width of the edge effect
return (float) (mMaxX - mCurrentX) / horizontalFadingEdgeLength;
} else {
// The distance from the maximum x position is more then the width of the fading edge so enable it fully.
return 1;
}
}
/** Determines the current fling absorb velocity */
private float determineFlingAbsorbVelocity() {
// If the OS version is high enough get the real velocity */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return IceCreamSandwichPlus.getCurrVelocity(mFlingTracker);
} else {
// Unable to get the velocity so just return a default.
// In actuality this is never used since EdgeEffectCompat does not draw anything unless the device is ICS+.
// Less then ICS EdgeEffectCompat essentially performs a NOP.
return FLING_DEFAULT_ABSORB_VELOCITY;
}
}
/** Use to schedule a request layout via a runnable */
private Runnable mDelayedLayout = new Runnable() {
@Override
public void run() {
requestLayout();
}
};
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Cache off the measure spec
mHeightMeasureSpec = heightMeasureSpec;
};
/**
* Determine the Max X position. This is the farthest that the user can scroll the screen. Until the last adapter item has been
* laid out it is impossible to calculate; once that has occurred this will perform the calculation, and if necessary force a
* redraw and relayout of this view.
*
* @return true if the maxx position was just determined
*/
private boolean determineMaxX() {
// If the last view has been laid out, then we can determine the maximum x position
if (isLastItemInAdapter(mRightViewAdapterIndex)) {
View rightView = getRightmostChild();
if (rightView != null) {
int oldMaxX = mMaxX;
// Determine the maximum x position
mMaxX = mCurrentX + (rightView.getRight() - getPaddingLeft()) - getRenderWidth();
// Handle the case where the views do not fill at least 1 screen
if (mMaxX < 0) {
mMaxX = 0;
}
if (mMaxX != oldMaxX) {
return true;
}
}
}
return false;
}
/** Adds children views to the left and right of the current views until the screen is full */
private void fillList(final int dx) {
// Get the rightmost child and determine its right edge
int edge = 0;
View child = getRightmostChild();
if (child != null) {
edge = child.getRight();
}
// Add new children views to the right, until past the edge of the screen
fillListRight(edge, dx);
// Get the leftmost child and determine its left edge
edge = 0;
child = getLeftmostChild();
if (child != null) {
edge = child.getLeft();
}
// Add new children views to the left, until past the edge of the screen
fillListLeft(edge, dx);
}
private void removeNonVisibleChildren(final int dx) {
View child = getLeftmostChild();
// Loop removing the leftmost child, until that child is on the screen
while (child != null && child.getRight() + dx <= 0) {
// The child is being completely removed so remove its width from the display offset and its divider if it has one.
// To remove add the size of the child and its divider (if it has one) to the offset.
// You need to add since its being removed from the left side, i.e. shifting the offset to the right.
mDisplayOffset += isLastItemInAdapter(mLeftViewAdapterIndex) ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();
// Add the removed view to the cache
recycleView(mLeftViewAdapterIndex, child);
// Actually remove the view
removeViewInLayout(child);
// Keep track of the adapter index of the left most child
mLeftViewAdapterIndex++;
// Get the new leftmost child
child = getLeftmostChild();
}
child = getRightmostChild();
// Loop removing the rightmost child, until that child is on the screen
while (child != null && child.getLeft() + dx >= getWidth()) {
recycleView(mRightViewAdapterIndex, child);
removeViewInLayout(child);
mRightViewAdapterIndex--;
child = getRightmostChild();
}
}
private void fillListRight(int rightEdge, final int dx) {
// Loop adding views to the right until the screen is filled
while (rightEdge + dx + mDividerWidth < getWidth() && mRightViewAdapterIndex + 1 < mAdapter.getCount()) {
mRightViewAdapterIndex++;
// If mLeftViewAdapterIndex < 0 then this is the first time a view is being added, and left == right
if (mLeftViewAdapterIndex < 0) {
mLeftViewAdapterIndex = mRightViewAdapterIndex;
}
// Get the view from the adapter, utilizing a cached view if one is available
View child = mAdapter.getView(mRightViewAdapterIndex, getRecycledView(mRightViewAdapterIndex), this);
addAndMeasureChild(child, INSERT_AT_END_OF_LIST);
// If first view, then no divider to the left of it, otherwise add the space for the divider width
rightEdge += (mRightViewAdapterIndex == 0 ? 0 : mDividerWidth) + child.getMeasuredWidth();
// Check if we are running low on data so we can tell listeners to go get more
determineIfLowOnData();
}
}
private void fillListLeft(int leftEdge, final int dx) {
// Loop adding views to the left until the screen is filled
while (leftEdge + dx - mDividerWidth > 0 && mLeftViewAdapterIndex >= 1) {
mLeftViewAdapterIndex--;
View child = mAdapter.getView(mLeftViewAdapterIndex, getRecycledView(mLeftViewAdapterIndex), this);
addAndMeasureChild(child, INSERT_AT_START_OF_LIST);
// If first view, then no divider to the left of it
leftEdge -= mLeftViewAdapterIndex == 0 ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();
// If on a clean edge then just remove the child, otherwise remove the divider as well
mDisplayOffset -= leftEdge + dx == 0 ? child.getMeasuredWidth() : mDividerWidth + child.getMeasuredWidth();
}
}
/** Loops through each child and positions them onto the screen */
private void positionChildren(final int dx) {
int childCount = getChildCount();
if (childCount > 0) {
mDisplayOffset += dx;
int leftOffset = mDisplayOffset;
// Loop each child view
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int left = leftOffset + getPaddingLeft();
int top = getPaddingTop();
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
// Layout the child
child.layout(left, top, right, bottom);
// Increment our offset by added child's size and divider width
leftOffset += child.getMeasuredWidth() + mDividerWidth;
}
}
}
/** Gets the current child that is leftmost on the screen. */
private View getLeftmostChild() {
return getChildAt(0);
}
/** Gets the current child that is rightmost on the screen. */
private View getRightmostChild() {
return getChildAt(getChildCount() - 1);
}
/**
* Finds a child view that is contained within this view, given the adapter index.
* @return View The child view, or or null if not found.
*/
private View getChild(int adapterIndex) {
if (adapterIndex >= mLeftViewAdapterIndex && adapterIndex <= mRightViewAdapterIndex) {
return getChildAt(adapterIndex - mLeftViewAdapterIndex);
}
return null;
}
/**
* Returns the index of the child that contains the coordinates given.
* This is useful to determine which child has been touched.
* This can be used for a call to {@link #getChildAt(int)}
*
* @param x X-coordinate
* @param y Y-coordinate
* @return The index of the child that contains the coordinates. If no child is found then returns -1
*/
private int getChildIndex(final int x, final int y) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++) {
getChildAt(index).getHitRect(mRect);
if (mRect.contains(x, y)) {
return index;
}
}
return -1;
}
我无法在此处发布所有代码。你们都知道为什么。请帮我。我有listview适配器,并且在mainactivity中有一些实例化。
请检查此链接以获取参考https://demonuts.com/horizontal-listview-android/。希望您在这里找到答案。