Android-Android GridView控件如何实现水平方向即左右滑动?

Android-Android GridView控件如何实现水平方向即左右滑动?

夜无邪 发布于 2016-12-29 字数 0 浏览 1375 回复 6

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

想挽留 2017-10-24 6 楼

可用 viewflipper 做左右滑动的效果,把GridView放在其中,自己来定义一些视图。

夜无邪 2017-09-21 5 楼

用ListView控件来控制横行滚动,实现代码:

/**
* 自定义支持横向滚动的ListView
*
*/
public class HVListView extends ListView {

/** 手势 */
private GestureDetector mGesture;
/** 列头 */
public LinearLayout mListHead;
/** 偏移坐标 */
private int mOffset = 0;
/** 屏幕宽度 */
private int screenWidth;

/** 构造函数 */
public HVListView(Context context, AttributeSet attrs) {
super(context, attrs);
mGesture = new GestureDetector(context, mOnGesture);
}

/** 分发触摸事件 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return mGesture.onTouchEvent(ev);
}

/** 手势 */
private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {

@Override
public boolean onDown(MotionEvent e) {
return true;
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}

/** 滚动 */
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
synchronized (HVListView.this) {
int moveX = (int) distanceX;
int curX = mListHead.getScrollX();
int scrollWidth = getWidth();
int dx = moveX;
//控制越界问题
if (curX + moveX < 0)
dx = 0;
if (curX + moveX + getScreenWidth() > scrollWidth)
dx = scrollWidth - getScreenWidth() - curX;

mOffset += dx;
//根据手势滚动Item视图
for (int i = 0, j = getChildCount(); i < j; i++) {
View child = ((ViewGroup) getChildAt(i)).getChildAt(1);
if (child.getScrollX() != mOffset)
child.scrollTo(mOffset, 0);
}
mListHead.scrollBy(dx, 0);
}
requestLayout();
return true;
}
};

/**
* 获取屏幕可见范围内最大屏幕
* @return
*/
public int getScreenWidth() {
if (screenWidth == 0) {
screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
if (getChildAt(0) != null) {
screenWidth -= ((ViewGroup) getChildAt(0)).getChildAt(0)
.getMeasuredWidth();
} else if (mListHead != null) {
//减去固定第一列
screenWidth -= mListHead.getChildAt(0).getMeasuredWidth();
}
}
return screenWidth;
}

/** 获取列头偏移量 */
public int getHeadScrollX() {
return mListHead.getScrollX();
}
}

代码说明:自定义HVListView继承自ListView,增加了横向手势监听,并在横向滚动时手动触发Layout容器内的滚动。

Activity :
public class TestHVListViewActivity extends Activity {

private LayoutInflater mInflater;

private HVListView mListView;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

mListView = (HVListView) findViewById(android.R.id.list);
//设置列头
mListView.mListHead = (LinearLayout) findViewById(R.id.head);
//设置数据
mListView.setAdapter(new DataAdapter());

mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
}

private class DataAdapter extends BaseAdapter {

@Override
public int getCount() {
return 50;//固定显示50行数据
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item, null);
}

for (int i = 0; i < 8; i++) {
((TextView) convertView.findViewById(R.id.item2 + i)).setText("数据" + position + "行" + (i + 2) + "列");
}

//校正(处理同时上下和左右滚动出现错位情况)
View child = ((ViewGroup) convertView).getChildAt(1);
int head = mListView.getHeadScrollX();
if (child.getScrollX() != head) {
child.scrollTo(mListView.getHeadScrollX(), 0);
}
return convertView;
}

@Override
public Object getItem(int position) {
return null;
}

@Override
public long getItemId(int position) {
return 0;
}
}
}

代码说明:为ListView提供了模拟数据。注意getView里面还有一段代码是校验,是专门处理同时横向和纵向滚动出现错位的情况。

main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:background="#eeffcc"
android:layout_width="wrap_content" android:layout_height="fill_parent">
<include layout="@layout/item" />
<com.nmbb.HVListView android:id="@android:id/list"
android:background="#FFB84D" android:fastScrollEnabled="true"
android:fadingEdgeLength="0.0sp" android:layout_width="1400.0dip"
android:layout_height="fill_parent" android:drawSelectorOnTop="false"
android:cacheColorHint="@null" android:dividerHeight="1.0dip">
</com.nmbb.HVListView>
</LinearLayout>

代码说明:注意这里需要指定HVListView的layout_width为滑动范围值,由item累加。

item.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:id="@+id/item1" android:text="不动列头1"
android:textSize="20.0sp" android:gravity="center"
android:layout_width="100.0dip" android:layout_height="wrap_content"></TextView>
<LinearLayout android:orientation="horizontal" android:id="@+id/head"
android:layout_width="1200.0dip" android:layout_height="wrap_content">
<TextView android:id="@+id/item2" android:text="不动列头2"
android:textColor="@android:color/black" android:textSize="20.0sp"
android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item3" android:text="不动列头3"
android:textSize="20.0sp" android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item4" android:text="不动列头4"
android:textColor="@android:color/black" android:textSize="20.0sp"
android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item5" android:text="不动列头5"
android:textSize="20.0sp" android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item6" android:text="不动列头6"
android:textColor="@android:color/black" android:textSize="20.0sp"
android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item7" android:text="不动列头7"
android:textSize="20.0sp" android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item8" android:text="不动列头8"
android:textColor="@android:color/black" android:textSize="20.0sp"
android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
<TextView android:id="@+id/item9" android:text="不动列头9"
android:textSize="20.0sp" android:singleLine="true" android:gravity="center"
android:layout_width="150.0dip" android:layout_height="wrap_content"></TextView>
</LinearLayout>
</LinearLayout>

代码说明:注意指定了每一个TextView的宽度为固定宽度,这样表格看起来就比较整齐。

晚风撩人 2017-07-10 4 楼

先贴一个老外做的实现:
主控件如下:

/*
* HorizontalListView.java v1.5
*
*
* The MIT License
* Copyright (c) 2011 Paul Soucy (paul@dev-smart.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/

package com.devsmart.android.ui;

import java.util.LinkedList;
import java.util.Queue;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.Scroller;

public class HorizontialListView extends AdapterView<ListAdapter> {

public boolean mAlwaysOverrideTouch = true;
protected ListAdapter mAdapter;
private int mLeftViewIndex = -1;
private int mRightViewIndex = 0;
protected int mCurrentX;
protected int mNextX;
private int mMaxX = Integer.MAX_VALUE;
private int mDisplayOffset = 0;
protected Scroller mScroller;
private GestureDetector mGesture;
private Queue<View> mRemovedViewQueue = new LinkedList<View>();
private OnItemSelectedListener mOnItemSelected;
private OnItemClickListener mOnItemClicked;
private boolean mDataChanged = false;

public HorizontialListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}

private synchronized void initView() {
mLeftViewIndex = -1;
mRightViewIndex = 0;
mDisplayOffset = 0;
mCurrentX = 0;
mNextX = 0;
mMaxX = Integer.MAX_VALUE;
mScroller = new Scroller(getContext());
mGesture = new GestureDetector(getContext(), mOnGesture);
}

@Override
public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
mOnItemSelected = listener;
}

@Override
public void setOnItemClickListener(AdapterView.OnItemClickListener listener){
mOnItemClicked = listener;
}

private DataSetObserver mDataObserver = new DataSetObserver() {

@Override
public void onChanged() {
synchronized(HorizontialListView.this){
mDataChanged = true;
}
invalidate();
requestLayout();
}

@Override
public void onInvalidated() {
reset();
invalidate();
requestLayout();
}

};

@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();
}

private synchronized void reset(){
initView();
removeAllViewsInLayout();
requestLayout();
}

@Override
public void setSelection(int position) {
//TODO: implement
}

private void addAndMeasureChild(final View child, int viewPos) {
LayoutParams params = child.getLayoutParams();
if(params == null) {
params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_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 oldCurrentX = mCurrentX;
initView();
removeAllViewsInLayout();
mNextX = oldCurrentX;
mDataChanged = false;
}

if(mScroller.computeScrollOffset()){
int scrollx = mScroller.getCurrX();
mNextX = scrollx;
}

if(mNextX < 0){
mNextX = 0;
mScroller.forceFinished(true);
}
if(mNextX > mMaxX) {
mNextX = mMaxX;
mScroller.forceFinished(true);
}

int dx = mCurrentX - mNextX;

removeNonVisibleItems(dx);
fillList(dx);
positionItems(dx);

mCurrentX = mNextX;

if(!mScroller.isFinished()){
post(new Runnable(){
@Override
public void run() {
requestLayout();
}
});

}
}

private void fillList(final int dx) {
int edge = 0;
View child = getChildAt(getChildCount()-1);
if(child != null) {
edge = child.getRight();
}
fillListRight(edge, dx);

edge = 0;
child = getChildAt(0);
if(child != null) {
edge = child.getLeft();
}
fillListLeft(edge, dx);

}

private void fillListRight(int rightEdge, final int dx) {
while(rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) {

View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, -1);
rightEdge += child.getMeasuredWidth();

if(mRightViewIndex == mAdapter.getCount()-1){
mMaxX = mCurrentX + rightEdge - getWidth();
}
mRightViewIndex++;
}

}

private void fillListLeft(int leftEdge, final int dx) {
while(leftEdge + dx > 0 && mLeftViewIndex >= 0) {
View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this);
addAndMeasureChild(child, 0);
leftEdge -= child.getMeasuredWidth();
mLeftViewIndex--;
mDisplayOffset -= child.getMeasuredWidth();
}
}

private void removeNonVisibleItems(final int dx) {
View child = getChildAt(0);
while(child != null && child.getRight() + dx <= 0) {
mDisplayOffset += child.getMeasuredWidth();
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mLeftViewIndex++;
child = getChildAt(0);

}

child = getChildAt(getChildCount()-1);
while(child != null && child.getLeft() + dx >= getWidth()) {
mRemovedViewQueue.offer(child);
removeViewInLayout(child);
mRightViewIndex--;
child = getChildAt(getChildCount()-1);
}
}

private void positionItems(final int dx) {
if(getChildCount() > 0){
mDisplayOffset += dx;
int left = mDisplayOffset;
for(int i=0;i<getChildCount();i++){
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
child.layout(left, 0, left + childWidth, child.getMeasuredHeight());
left += childWidth;
}
}
}

public synchronized void scrollTo(int x) {
mScroller.startScroll(mNextX, 0, x - mNextX, 0);
requestLayout();
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = mGesture.onTouchEvent(ev);
return handled;
}

protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
synchronized(HorizontialListView.this){
mScroller.fling(mNextX, 0, (int)-velocityX, 0, 0, mMaxX, 0, 0);
}
requestLayout();

return true;
}

protected boolean onDown(MotionEvent e) {
mScroller.forceFinished(true);
return true;
}

private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() {

@Override
public boolean onDown(MotionEvent e) {
return HorizontialListView.this.onDown(e);
}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return HorizontialListView.this.onFling(e1, e2, velocityX, velocityY);
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {

synchronized(HorizontialListView.this){
mNextX += (int)distanceX;
}
requestLayout();

return true;
}

@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Rect viewRect = new Rect();
for(int i=0;i<getChildCount();i++){
View child = getChildAt(i);
int left = child.getLeft();
int right = child.getRight();
int top = child.getTop();
int bottom = child.getBottom();
viewRect.set(left, top, right, bottom);
if(viewRect.contains((int)e.getX(), (int)e.getY())){
if(mOnItemClicked != null){
mOnItemClicked.onItemClick(HorizontialListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));
}
if(mOnItemSelected != null){
mOnItemSelected.onItemSelected(HorizontialListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId( mLeftViewIndex + 1 + i ));
}
break;
}

}
return true;
}
};
}

调用端:

 package com.devsmart.android.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.devsmart.android.ui.HorizontialListView;

public class HorizontalListViewDemo extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.listviewdemo);

HorizontialListView listview = (HorizontialListView) findViewById(R.id.listview);

listview.setAdapter(mAdapter);

}

private static String[] dataObjects = new String[]{ "Text #1",
"Text #2",

"Text #3" };

private BaseAdapter mAdapter = new BaseAdapter() {

@Override
public int getCount() {
return dataObjects.length;

}

@Override
public Object getItem(int position) {
return null;

}

@Override
public long getItemId(int position) {
return 0;

}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View retval = LayoutInflater.from(parent.getContext()).inflate(R.layout.viewitem, null);
TextView title = (TextView) retval.findViewById(R.id.title);

title.setText(dataObjects[position]);

return retval;

}

};

}

res/layout/listviewdemo.xml:

 <!--?xml version="1.0" encoding="utf-8"?-->

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background=" #fff">

<com.devsmart.android.ui.horizontiallistview android:id="@+id/listview" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background=" #ddd">

</com.devsmart.android.ui.horizontiallistview></linearlayout>

res/layout/listitem.xml:

 <!--?xml version="1.0" encoding="utf-8"?-->

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background=" #fff">

<imageview android:id="@+id/image" android:layout_width="150dip" android:layout_height="150dip" android:scaletype="centerCrop" android:src="@drawable/icon">

<textview android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textcolor=" #000" android:gravity="center_horizontal">

</textview></imageview></linearlayout>

清晨说ぺ晚安 2017-06-25 3 楼

我之前的一个项目里面。刚好有一个这个功能。
先看图:

把GridView构建出来放入ViewFlipper中,然后使用GestureDetector来实现滑动手势,左右滑动手势触发后通过:mViewFlipper.showPrevious();和mViewFlipper.showNext();来切换不同GridView;在mViewFlipper.setInAnimation和mViewFlipper.setOutAnimation进入动画效果。

想挽留 2017-03-07 2 楼

模拟launcher实现过这种效果。

浮生未歇 2017-01-20 1 楼

这个我也没有实现过,不过可以提供下思路,Android没有提供标准的左右移动的GridView的方法,不过可以自己来模拟一个类似苹果的那种左右移动的方式,我得思路如下:

把GridView只是作为一个数据显示的屏幕,每次只提供单屏幕的数据。
计算一页显示数据的个数,根据总个数计算页数,根据每个item的宽度计算滚动的步进。
OnScreenChangeListenerDataLoad的事件处理,在事件中更新新页面的数据。
处理ScrollLayout的ACTION_DOWN、ACTION_MOVE、ACTION_UP在这些事件中重新计算显示的内容和处理步进。