Android 各种 滚动View 监听 和各种判断

总结下各种View 的滑动监听

滑动阈值:int touchSlop = ViewConfiguration.get(this).getScaledTouchSlop();

getMeasuredHeight()是实际View的大小,与屏幕无关,而getHeight的大小此时则是屏幕的大小。

当超出屏幕后, getMeasuredHeight() 等于 getHeight()加上屏幕之外没有显示的大小

滚动到顶部判断:
getScrollY() == 0

滚动到底部判断:
View childView = getChildAt(0);

childView.getMeasuredHeight() <= getScrollY() + getHeight();

其中getChildAt表示得到ScrollView的child View
childView.getMeasuredHeight()表示得到子View的高度,

getScrollY()表示得到y轴的滚动距离,

getHeight()为scrollView可见的高度即屏幕的高度
getScrollY()达到*大时加上scrollView的高度就的就等于它内容的高度了.

ScroolView:
回到顶部:

一、ScrollView.scrollTo(0,0)  直接置顶,瞬间回到顶部,没有滚动过程,其中Y值可以设置为大于0的值,使Scrollview停在指定位置;

二、ScrollView.fullScroll(View.FOCUS_UP)  类似于手动拖回顶部,有滚动过程;   需要post 新开线程

三、ScrollView.smoothScrollTo(0, 0) 类似于手动拖回顶部,有滚动过程,其中Y值可以设置为大于0的值,使Scrollview停在指定位置。
判断滑动位置的地方,可以有两种方式:

1、实现OnTouchListener来监听

OnTouchListener onTouchListener=new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (childView != null && childView .getMeasuredHeight() <= getScrollY() + getHeight()) {
// 滑动到底部
} else if (getScrollY() == 0) {
// 滑动到顶部 貌似不好用
//看下面listview 判断 *个子view 的getTop
}
break;
}
return false;
}
}
2、重写ScrollView的onScrollChanged的方法,在onScrollChanged函数中判断

public class myScrollView extends ScrollView
{
public myScrollView(Context context)
{
super(context);
}
public myScrollView(Context context, AttributeSet attributeSet)
{
super(context,attributeSet);
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
View view = (View)getChildAt(getChildCount()-1);
int d = view.getBottom();
d -= (getHeight()+getScrollY());
if(d==0)
{
//you are at the end of the list in scrollview
//do what you wanna do here
}
else
super.onScrollChanged(l,t,oldl,oldt);
}
}
判断是否在顶部  在滚动停止后:

private int lastY = 0;
private int touchEventId = -9983761;

@SuppressLint(“HandlerLeak”)
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
View scroller = (View) msg.obj;

if (msg.what == touchEventId) {
if (lastY == scroller.getScrollY()) {
//停止了,此处你的操作业务
if (scroller.getScrollY() == 0) {
hidePopup();
}
} else {
handler.sendMessageDelayed(handler.obtainMessage(touchEventId, scroller), 1);
lastY = scroller.getScrollY();
}
}
}
};
case MotionEvent.ACTION_CANCEL:
if (type == SCROLLVIEW) {
// hidePopup(); 从整个Messge池中返回一个新的Message实例 降低内存的开销 避免分配新的对象
handler.sendMessageDelayed(handler.obtainMessage(touchEventId, v), 300);
}
break;
判断滑动方向:

1. onTouch 监听

package com.example.testtt;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.Toast;
public class MainActivity extends Activity {
//手指按下的点为(x1, y1)手指离开屏幕的点为(x2, y2)
float x1 = 0;
float x2 = 0;
float y1 = 0;
float y2 = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
//继承了Activity的onTouchEvent方法,直接监听点击事件
if(event.getAction() == MotionEvent.ACTION_DOWN) {
//当手指按下的时候
x1 = event.getX();
y1 = event.getY();
}
if(event.getAction() == MotionEvent.ACTION_UP) {
//当手指离开的时候
x2 = event.getX();
y2 = event.getY();
if(y1 – y2 > 50) {
Toast.makeText(MainActivity.this, “向上滑”, Toast.LENGTH_SHORT).show();
} else if(y2 – y1 > 50) {
Toast.makeText(MainActivity.this, “向下滑”, Toast.LENGTH_SHORT).show();
} else if(x1 – x2 > 50) {
Toast.makeText(MainActivity.this, “向左滑”, Toast.LENGTH_SHORT).show();
} else if(x2 – x1 > 50) {
Toast.makeText(MainActivity.this, “向右滑”, Toast.LENGTH_SHORT).show();
}
}
return super.onTouchEvent(event);
}

}
2. 继承SrcoolView

/使用自定义view继承自ScrollView
public class MyScrollView extends ScrollView {

private OnScrollListener listener;

public void setOnScrollListener(OnScrollListener listener) {
this.listener = listener;
}

public MyScrollView(Context context) {
super(context);
}

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

public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

//设置接口
public interface OnScrollListener{
void onScroll(int scrollY);
}

//重写原生onScrollChanged方法,将参数传递给接口,由接口传递出去
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(listener != null){

//这里我只传了垂直滑动的距离
listener.onScroll(t);
}
}
}

//1. *个参数是目前水平滑动后的距离
//2. 第二个参数是目前垂直滑动后的距离
//3. 第三个参数是之前水平滑动前的距离
//4. 第四个参数是之前水平滑动前的距离
ListView 和GridView 监听
移动到顶部底部:

listview.setSelection(position);

gridview.smoothScrollToPosition(position);  带动画   post新开线程

需要在新线程使用

滚动监听:

private AbsListView.OnScrollListener LIST_GRID_LISTENER = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_FLING:
break;
case SCROLL_STATE_IDLE:
// 判断滚动到顶部
if (mListView.getFirstVisiblePosition() == 0) {
View firstVisibleItemView = view.getChildAt(0);
if (firstVisibleItemView != null && firstVisibleItemView.getTop() == 0) {

}
}
break;
case SCROLL_STATE_TOUCH_SCROLL:

break;
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0) {

//顶部 不够准确
}
}
};
ScroolState 三种状态

OnScrollListener.SCROLL_STATE_IDLE:滚动停止时的状态
OnScrollListener.SCROLL_STATE_STOUCH_SCROLL:触摸正在滚动,手指还没离开界面时的状态
OnScrollListener.SCROLL_STATE_FLING:用户在用力滑动后,ListView由于惯性将继续滑动时的状态

onScroll 介绍

firstVisibleItem:当前能看见的*个item的ID(从0开始)
visibleItemCount:当前可见的item总数
totalItemCount:列表中适配器总数量,也就是整个ListView中item总数
滑动方向:

firstVisibleItem > oldVisibleItem   // 向上滑动
firstVisibleItem < oldVisibleItem  // 向下滑动
判断顶部底部

firstVisibleItem + visibleItemCount == totalItemCount    //底部
firstVisibleItem=0  顶部
在onScrollStateChanged中判断
mListView.getLastVisiblePosition() == (mListView.getCount() – 1)  //底部
mListView.getFirstVisiblePosition() == 0   //顶部
ListView也为我们提供了一些封装好了的方法,来获取item的位置信息

// 获取当前可见区域内*个item            mListView.getFirstVisiblePosition();

// 获取当前可见区域内*后一个item       mListView.getLastVisiblePosition();

 

RecyclerView :
移动:

RecyclerView提供了几种移动的方法

scrollToPosition       这个方法的作用是定位到指定项,就是把你想显示的项显示出来,但是在屏幕的什么位置是不管的,只要那一项现在看得到了,那它就罢工了!

scrollTo

scrollBy   这个方法是自己去控制移动的距离,单位是像素,所以在使用scrollBy(x, y)需要自己去计算移动的高度或宽度。

smoothScrollBy

smoothScrollToPosition      惯性滑动至某一位置

scrollToPositionWithOffset   用法:((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(position,0);

该置顶就置顶

判断 顶部 底部
方法一:

垂直判断:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.i(TAG, “————————————–“);
if(mRecyclerView.canScrollVertically(1)){
Log.i(TAG, “direction 1: true”);
}else {
Log.i(TAG, “direction 1: false”);//滑动到底部
}
if(mRecyclerView.canScrollVertically(-1)){
Log.i(TAG, “direction -1: true”);
}else {
Log.i(TAG, “direction -1: false”);//滑动到顶部
}
}
});
横向:

/**
* Check if this view can be scrolled horizontally in a certain direction.
*
* @param direction Negative to check scrolling left, positive to check scrolling right.
* @return true if this view can be scrolled in the specified direction, false otherwise.
*/
public boolean canScrollHorizontally(int direction) {
final int offset = computeHorizontalScrollOffset();
final int range = computeHorizontalScrollRange() – computeHorizontalScrollExtent();
if (range == 0) return false;
if (direction < 0) {
return offset > 0;
} else {
return offset < range – 1;
}
}
判断是否滑动到底部, recyclerView.canScrollVertically(1);返回false表示不能往上滑动,即代表到底部了;

判断是否滑动到顶部, recyclerView.canScrollVertically(-1);返回false表示不能往下滑动,即代表到顶部了;

方法二:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.i(TAG, “————————————–“);
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition();
Log.i(TAG, “firstCompletelyVisibleItemPosition: “+firstCompletelyVisibleItemPosition);
if(firstCompletelyVisibleItemPosition==0)
Log.i(TAG, “滑动到顶部”);

int lastCompletelyVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition();
Log.i(TAG, “lastCompletelyVisibleItemPosition: “+lastCompletelyVisibleItemPosition);
if(lastCompletelyVisibleItemPosition==layoutManager.getItemCount()-1)
Log.i(TAG, “滑动到底部”);
}
});
滑动方向:

//dy <0 表示 上滑, dy>0 表示下滑

三种状态:

/**
* The RecyclerView is not currently scrolling.
* 当前的recycleView不滑动(滑动已经停止时)
*/
public static final int SCROLL_STATE_IDLE = 0;

/**
* The RecyclerView is currently being dragged by outside input such as user touch input.
* 当前的recycleView被拖动滑动
*/
public static final int SCROLL_STATE_DRAGGING = 1;

/**
* The RecyclerView is currently animating to a final position while not under
* outside control.
* 当前的recycleView在滚动到某个位置的动画过程,但没有被触摸滚动.调用 scrollToPosition(int) 应该会触发这个状态
*/
public static final int SCROLL_STATE_SETTLING = 2;

使用RecyclerView实现多行水平分页的GridView效果和ViewPager效果

前些天看到有人在论坛上问这种效果怎么实现,没写过也没用过这个功能,网上查了一下,大多是使用ViewPager+GridView或者HorizontalScrollView+GridView实现,不过貌似有点复杂,太懒,没仔细看。这两天学习RecyclerView的使用(网上有很多文章,建议大家阅读本博客的时候先去了解一下),发现RecyclerView可以实现GridView 的横向滚动效果,不过没有分页,因此决定自己写一个。

博文*后也有贴出完整代码,使用Eclipse的同学可以自己新建项目并Copy代码。

效果图:
(由于这里每个Item都很相像,所以效果看起来不是很好,请见谅)
图1:
在这里插入图片描述
图2:
在这里插入图片描述
(删除的操作是在长按事件中写的)
图1是带页码指示器的多行横向分页的GridView效果,拖动距离不足时,还可以滚动回原来的位置(类似于ViewPager拖动距离不足的效果);
图2是和ViewPager一模一样的效果,实现此效果只要设置行数和列数都为1即可。

代码结构:

在这里插入图片描述

  • AutoGridLayoutManager继承自GridLayoutManager并重写了onMeasure方法,目的是使RecyclerView的高度自适应内容高度。
  • DimensionConvert是一个用来转换px和pd的工具类。
  • MainActivity是一个使用示例。
  • PageIndicatorView继承自LinearLayout,存放一些小圆点作为页码指示器。
  • PageRecyclerView继承自RecyclerView,用来完成分页等功能。

先简单讲一下实现步骤,之后贴完整的代码

*步: 实现横向滚动的GridView效果

这个很简单,只要给RecyclerView设置横向的GridLayoutManager就可以了。但是使用过程中发现,RecyclerView并不会自适应内容的高度,因此重写了GridLayoutManager的onMeasure方法(MyGridLayoutManager.java);

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    View view = recycler.getViewForPosition(0);
    if (view != null) {
        measureChild(view, widthSpec, heightSpec);
        int measuredWidth = View.MeasureSpec.getSize(widthSpec);
        int measuredHeight = view.getMeasuredHeight() * getSpanCount();
        setMeasuredDimension(measuredWidth, measuredHeight);
    }
}

 

第二步:实现自定义行数和列数功能

实现此功能需要重写RecyclerView(MyRecyclerView.java),并添加两个成员变量spanRowspanColumn和一个设置行数列数的方法setPageSize(int spanRow, int spanColumn)
之后,在Adapter中生成Item的时候就可以根据设置好的PageRecyclerView的宽度和列数计算单个Item的宽度,以达到一页正好显示固定列数的目的:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (itemWidth <= 0) {
        // 计算Item的宽度
        itemWidth = (parent.getWidth() - pageMargin * 2) / spanColumn;
    }

    RecyclerView.ViewHolder holder = mCallBack.onCreateViewHolder(parent, viewType);

    holder.itemView.measure(0, 0);
    holder.itemView.getLayoutParams().width = itemWidth;
    holder.itemView.getLayoutParams().height = holder.itemView.getMeasuredHeight();

    return holder;
}

 

可以看到上面代码中有一个mCallBack变量,这是一个接口的实现类的实例,我们需要创建Adapter实例的时候传入一个此接口的子类实例。

public interface CallBack {

    /**
     * 创建VieHolder
     *
     * @param parent
     * @param viewType
     */
    RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

    /**
     * 绑定数据到ViewHolder
     *
     * @param holder
     * @param position
     */
    void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

}

 

此接口共有两个方法,这两个方法和Adapter中需要重写的两个方法一样,用法也一样,分别用来创建ViewHolder实例和给ViewHolder中的控件绑定数据。

第三步:开始分页滚动

1> 分页:
完成第二步之后,布局就调整好了,之后我们实现分页滚动的功能。要分页就肯定需要总页数(totalPage)和当前页码(currentPage),我们需要在设置Adapter适配器之后根据Item的总数和每页的Item数计算总页数:

@Override
public void setAdapter(Adapter adapter) {
    super.setAdapter(adapter);
    // 计算总页数
    totalPage = ((int) Math.ceil(adapter.getItemCount() / (double) (spanRow * spanColumn)));
    mIndicatorView.initIndicator(totalPage);
}

 

然后就可以重写RecyclerView的onTouchEvent方法实现分页,根据ACTION_DOWNACTION_UP时候的坐标计算滑动方向,在ACTION_UP的时候根据滑动的方向使用smoothScrollBy方法向左或向右滑动一个MyRecyclerView的宽度就可以了。
不过这种切换页面的方式很生硬,我们要实现的ViewPager的滑动效果:要滑动超过一定的距离才能切换页码,否则滚回原来的位置。实现此功能需要一个常量,不过为了适应各种宽度的MyRecyclerView,这里根据MyRecyclerView的宽度动态设置*小滚动距离:

private int shortestDistance; // 超过此距离的滑动才有效

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    super.onMeasure(widthSpec, heightSpec);
    shortestDistance = getMeasuredWidth() / 3;
}

 

还需要其他的几个变量:

private float downX = 0; // 手指按下的X轴坐标
private float slideDistance = 0; // 滑动的距离
private float scrollX = 0; // X轴当前的位置

 

scrollX为当前滚动的位置,重写onScrolled计算滚动到的位置:

@Override
public void onScrolled(int dx, int dy) {
    scrollX += dx;
    super.onScrolled(dx, dy);
}

 

之后就可以编写完整的onTouchEvent方法

@Override
public boolean onTouchEvent(MotionEvent event) {

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = event.getX();
            break;
        case MotionEvent.ACTION_UP:
            slideDistance = event.getX() - downX;
            if (Math.abs(slideDistance) > shortestDistance) {
                // 滑动距离足够,执行翻页
                if (slideDistance > 0) {
                    // 上一页
                    currentPage = currentPage == 1 ? 1 : currentPage - 1;
                } else {
                    // 下一页
                    currentPage = currentPage == totalPage ? totalPage : currentPage + 1;
                }
            }
            // 执行滚动
            smoothScrollBy((int) ((currentPage - 1) * getWidth() - scrollX), 0);
            return true;
        default:
            break;
    }

    return super.onTouchEvent(event);
}

2> 页间距

为了分页更加清晰,还需要给页与页添加间距:
首先添加一个成员变量,和set方法

private int pageMargin = 0; // 页间距
/**
 * 设置页间距
 *
 * @param pageMargin 间距(px)
 */
public void setPageMargin(int pageMargin) {
    this.pageMargin = pageMargin;
}

 

然后重写Adapter的onBindViewHolder方法调整页间距:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (spanColumn == 1) {
        // 每个Item距离左右两侧各pageMargin
        holder.itemView.getLayoutParams().width = itemWidth + pageMargin * 2;
        holder.itemView.setPadding(pageMargin, 0, pageMargin, 0);
    } else {
        int m = position % (spanRow * spanColumn);
        if (m < spanRow) {
            // 每页左侧的Item距离左边pageMargin
            holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
            holder.itemView.setPadding(pageMargin, 0, 0, 0);
        } else if (m >= spanRow * spanColumn - spanRow) {
            // 每页右侧的Item距离右边pageMargin
            holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
            holder.itemView.setPadding(0, 0, pageMargin, 0);
        } else {
            // 中间的正常显示
            holder.itemView.getLayoutParams().width = itemWidth;
            holder.itemView.setPadding(0, 0, 0, 0);
        }
    }

}

 

3> 占位Item

为了*后不足一页时也能完整显示,还需要在*后不足一页时,生成占位的View,因此修改Adapter的onBindViewHolder方法和getItemCount方法:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

    ...

    if (position < dataList.size()) {
        holder.itemView.setAlpha(1);
        mCallBack.onBindViewHolder(holder, position);
    } else {
        holder.itemView.setAlpha(0);
    }
}

@Override
public int getItemCount() {
    int m = dataList.size() % (spanRow * spanColumn);
    if (m == 0) {
        return dataList.size();
    } else {
       return dataList.size() + (spanRow * spanColumn - m);
   }
}

至此,分页功能就完成了,为了功能更丰满,还需要添加一个分页指示器(就是效果图中的小圆点),这个功能还是很简单的,新建一个类继承LinearLayout并根据总页数生成一些小圆点的View,然后提供一个修改当前页码的方法就OK啦。

第四步:删除Item

*后还有一个删除Item的功能,实现方式还是使用系统的Adapter的notifyItemRemoved(int position);方法,由于前面分页时给部分Item设置了padding,所以为了布局不会错乱,还需要更新其他改变的Item:

// 删除Item
notifyItemRemoved(position);
// 更新界面上发生改变的Item
notifyItemRangeChanged(position, currentPage * spanRow * spanColumn);

 

然后还要更新页码指示器,这里就不贴代码了,直接看下面的类就可以了。
使用的时候只要把指示器和MyRecyclerView按照自己的需求布局,并在切换页面的时候更新指示器就完成了。

改:

  1. 上面分页滑动是在onTouchEvent()方法中实现的,但是后来发现,这种实现方式会导致给Item添加onClickListeneronLongClickListeneronTouchListener的时候会产生事件冲突,因此修改为在onScrollStateChanged()方法中实现,代码如下:
/*
     * 0: 停止滚动且手指移开; 1: 开始滚动; 2: 手指做了抛的动作(手指离开屏幕前,用力滑了一下)
     */
private int scrollState = 0; // 滚动状态
@Override
public void onScrollStateChanged(int state) {
    switch (state) {
        case 2:
            scrollState = 2;
            break;
        case 1:
            scrollState = 1;
            break;
        case 0:
            if (slideDistance == 0) {
                break;
            }
            scrollState = 0;
            if (slideDistance < 0) { // 上页
                currentPage = (int) Math.ceil(scrollX / getWidth());
                if (currentPage * getWidth() - scrollX < shortestDistance) {
                    currentPage += 1;
                }
            } else { // 下页
                currentPage = (int) Math.ceil(scrollX / getWidth()) + 1;
                if (currentPage <= totalPage) {
                    if (scrollX - (currentPage - 2) * getWidth() < shortestDistance) {
                        // 如果这一页滑出距离不足,则定位到前一页
                        currentPage -= 1;
                    }
                } else {
                    currentPage = totalPage;
                }
            }
            // 执行自动滚动
            smoothScrollBy((int) ((currentPage - 1) * getWidth() - scrollX), 0);
            // 修改指示器选中项
            mIndicatorView.setSelectedPage(currentPage - 1);
            slideDistance = 0;
            break;
    }
    super.onScrollStateChanged(state);
}

@Override
public void onScrolled(int dx, int dy) {
    scrollX += dx;
    if (scrollState == 1) {
        slideDistance += dx;
    }

    super.onScrolled(dx, dy);
}

 

RecyclerView的GridLayoutManager是从上到下从左到右排列的,而我们分页时大多需要的是从左到右从上到下排列,因此增加一个方法调整位置(此方法只适用于3*3排列的,还没有找到通用的方法,如果那位同学有方法,麻烦分享一下,先谢过)

private void countRealPosition(int position) {
    // 为了使Item从左到右从上到下排列,需要position的值
    int m = position % (spanRow * spanColumn);
    switch (m) {
        case 1:
        case 5:
            realPosition = position + 2;
            break;
        case 3:
        case 7:
            realPosition = position - 2;
            break;
        case 2:
            realPosition = position + 4;
            break;
        case 6:
            realPosition = position - 4;
            break;
        case 0:
        case 4:
        case 8:
            realPosition = position;
            break;
    }
}

 

<<<<<<<<<<<<<<<<<<<<<<使用方法参考MainActivity.java>>>>>>>>>>>>>>>>>>>>

上面讲的不够详细,具体见代码>>>>>>>>>>>>>>

完整代码:

AutoGridLayoutManager.java

使用这个类替代GridLayoutManager是为了使RecyclerView及其子类能够自适应内容的高度。

import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by shichaohui on 2015/7/9 0009.
 * <p>
 * 重写GridLayoutManager,在{@link RecyclerView#setLayoutManager(RecyclerView.LayoutManager)}使用
 * 此类替换{@link GridLayoutManager},使{@link RecyclerView}能够自使用内容的高度
 * </p>
 */
public class AutoGridLayoutManager extends GridLayoutManager {

    private int measuredWidth = 0;
    private int measuredHeight = 0;

    public AutoGridLayoutManager(Context context, AttributeSet attrs,
                                 int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public AutoGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public AutoGridLayoutManager(Context context, int spanCount,
                                 int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }

    @Override
    public void onMeasure(RecyclerView.Recycler recycler,
                          RecyclerView.State state, int widthSpec, int heightSpec) {
        if (measuredHeight <= 0) {
            View view = recycler.getViewForPosition(0);
            if (view != null) {
                measureChild(view, widthSpec, heightSpec);
                measuredWidth = View.MeasureSpec.getSize(widthSpec);
                measuredHeight = view.getMeasuredHeight() * getSpanCount();
            }
        }
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

}

 

PageRecyclerView.java

重写RecyclerView实现分页

import android.content.Context;
import android.graphics.Color;
import android.support.v4.view.PagerAdapter;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;
import java.util.Objects;

/**
 * Created by shichaohui on 2015/7/9 0009.
 * <p>
 * 横向分页的GridView效果
 * </p>
 * <p>
 * 默认为1行,每页3列,如果要自定义行数和列数,请在调用{@link PageRecyclerView#setAdapter(Adapter)}方法前调用
 * {@link PageRecyclerView#setPageSize(int, int)}方法自定义行数
 * </p>
 */
public class PageRecyclerView extends RecyclerView {

    private Context mContext = null;

    private PageAdapter myAdapter = null;

    private int shortestDistance; // 超过此距离的滑动才有效
    private float downX = 0; // 手指按下的X轴坐标
    private float slideDistance = 0; // 滑动的距离
    private float scrollX = 0; // X轴当前的位置

    private int spanRow = 1; // 行数
    private int spanColumn = 3; // 每页列数
    private int totalPage = 0; // 总页数
    private int currentPage = 1; // 当前页

    private int pageMargin = 0; // 页间距

    private PageIndicatorView mIndicatorView = null; // 指示器布局

    public PageRecyclerView(Context context) {
        this(context, null);
    }

    public PageRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PageRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        defaultInit(context);
    }

    // 默认初始化
    private void defaultInit(Context context) {
        this.mContext = context;
        setLayoutManager(new AutoGridLayoutManager(
                mContext, spanRow, AutoGridLayoutManager.HORIZONTAL, false));
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    /**
     * 设置行数和每页列数
     *
     * @param spanRow    行数,<=0表示使用默认的行数
     * @param spanColumn 每页列数,<=0表示使用默认每页列数
     */
    public void setPageSize(int spanRow, int spanColumn) {
        this.spanRow = spanRow <= 0 ? this.spanRow : spanRow;
        this.spanColumn = spanColumn <= 0 ? this.spanColumn : spanColumn;
        setLayoutManager(new AutoGridLayoutManager(
                mContext, this.spanRow, AutoGridLayoutManager.HORIZONTAL, false));
    }

    /**
     * 设置页间距
     *
     * @param pageMargin 间距(px)
     */
    public void setPageMargin(int pageMargin) {
        this.pageMargin = pageMargin;
    }

    /**
     * 设置指示器
     *
     * @param indicatorView 指示器布局
     */
    public void setIndicator(PageIndicatorView indicatorView) {
        this.mIndicatorView = indicatorView;
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        super.onMeasure(widthSpec, heightSpec);
        shortestDistance = getMeasuredWidth() / 3;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        this.myAdapter = (PageAdapter) adapter;
        update();
    }

    // 更新页码指示器和相关数据
    private void update() {
        // 计算总页数
        int temp = ((int) Math.ceil(myAdapter.dataList.size() / (double) (spanRow * spanColumn)));
        if (temp != totalPage) {
            mIndicatorView.initIndicator(temp);
            // 页码减少且当前页为*后一页
            if (temp < totalPage && currentPage == totalPage) {
                currentPage = temp;
                // 执行滚动
                smoothScrollBy(-getWidth(), 0);
            }
            mIndicatorView.setSelectedPage(currentPage - 1);
            totalPage = temp;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                if (currentPage == totalPage && downX - event.getX() > 0) {
                    return true;
                }
                break;
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                break;
            case MotionEvent.ACTION_UP:
                slideDistance = event.getX() - downX;
                if (Math.abs(slideDistance) > shortestDistance) {
                    // 滑动距离足够,执行翻页
                    if (slideDistance > 0) {
                        // 上一页
                        currentPage = currentPage == 1 ? 1 : currentPage - 1;
                    } else {
                        // 下一页
                        currentPage = currentPage == totalPage ? totalPage : currentPage + 1;
                    }
                    // 修改指示器选中项
                    mIndicatorView.setSelectedPage(currentPage - 1);
                }
                // 执行滚动
                smoothScrollBy((int) ((currentPage - 1) * getWidth() - scrollX), 0);
                return true;
            default:
                break;
        }

        return super.onTouchEvent(event);
    }

    @Override
    public void onScrolled(int dx, int dy) {
        scrollX += dx;
        super.onScrolled(dx, dy);
    }

    /**
     * 数据适配器
     */
    public class PageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        private List<?> dataList = null;
        private CallBack mCallBack = null;
        private int itemWidth = 0;
        private int itemCount = 0;

        /**
         * 实例化适配器
         *
         * @param data
         * @param callBack
         */
        public PageAdapter(List<?> data, CallBack callBack) {
            this.dataList = data;
            this.mCallBack = callBack;
            itemCount = dataList.size() + spanRow * spanColumn;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (itemWidth <= 0) {
                // 计算Item的宽度
                itemWidth = (parent.getWidth() - pageMargin * 2) / spanColumn;
            }

            RecyclerView.ViewHolder holder = mCallBack.onCreateViewHolder(parent, viewType);

            holder.itemView.measure(0, 0);
            holder.itemView.getLayoutParams().width = itemWidth;
            holder.itemView.getLayoutParams().height = holder.itemView.getMeasuredHeight();

            return holder;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (spanColumn == 1) {
                // 每个Item距离左右两侧各pageMargin
                holder.itemView.getLayoutParams().width = itemWidth + pageMargin * 2;
                holder.itemView.setPadding(pageMargin, 0, pageMargin, 0);
            } else {
                int m = position % (spanRow * spanColumn);
                if (m < spanRow) {
                    // 每页左侧的Item距离左边pageMargin
                    holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
                    holder.itemView.setPadding(pageMargin, 0, 0, 0);
                } else if (m >= spanRow * spanColumn - spanRow) {
                    // 每页右侧的Item距离右边pageMargin
                    holder.itemView.getLayoutParams().width = itemWidth + pageMargin;
                    holder.itemView.setPadding(0, 0, pageMargin, 0);
                } else {
                    // 中间的正常显示
                    holder.itemView.getLayoutParams().width = itemWidth;
                    holder.itemView.setPadding(0, 0, 0, 0);
                }
            }

            if (position < dataList.size()) {
                holder.itemView.setVisibility(View.VISIBLE);
                mCallBack.onBindViewHolder(holder, position);
            } else {
                holder.itemView.setVisibility(View.INVISIBLE);
            }

        }

        @Override
        public int getItemCount() {
            return itemCount;
        }

        /**
         * 删除Item
         * @param position 位置
         */
        public void remove(int position) {
            if (position < dataList.size()) {
                // 删除数据
                dataList.remove(position);
                itemCount--;
                // 删除Item
                notifyItemRemoved(position);
                // 更新界面上发生改变的Item
                notifyItemRangeChanged(position, currentPage * spanRow * spanColumn);
                // 更新页码指示器
                update();
            }
        }

    }

    public interface CallBack {

        /**
         * 创建VieHolder
         *
         * @param parent
         * @param viewType
         */
        RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

        /**
         * 绑定数据到ViewHolder
         *
         * @param holder
         * @param position
         */
        void onBindViewHolder(RecyclerView.ViewHolder holder, int position);

    }

}

 

PageIndicatorView.java

页码指示器 ,此类可以作为一个工具类,在ViewPager做的轮播图上也可以使用

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by shichaohui on 2015/7/10 0010.
 * <p/>
 * 页码指示器类,获得此类实例后,可通过{@link PageIndicatorView#initIndicator(int)}方法初始化指示器
 * </P>
 */
public class PageIndicatorView extends LinearLayout {

    private Context mContext = null;
    private int dotSize = 15; // 指示器的大小(dp)
    private int margins = 4; // 指示器间距(dp)
    private List<View> indicatorViews = null; // 存放指示器

    public PageIndicatorView(Context context) {
        this(context, null);
    }

    public PageIndicatorView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PageIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        this.mContext = context;

        setGravity(Gravity.CENTER);
        setOrientation(HORIZONTAL);

        dotSize = DimensionConvert.dip2px(context, dotSize);
        margins = DimensionConvert.dip2px(context, margins);
    }

    /**
     * 初始化指示器,默认选中*页
     *
     * @param count 指示器数量,即页数
     */
    public void initIndicator(int count) {

        if (indicatorViews == null) {
            indicatorViews = new ArrayList<>();
        } else {
            indicatorViews.clear();
            removeAllViews();
        }
        View view;
        LayoutParams params = new LayoutParams(dotSize, dotSize);
        params.setMargins(margins, margins, margins, margins);
        for (int i = 0; i < count; i++) {
            view = new View(mContext);
            view.setBackgroundResource(android.R.drawable.presence_invisible);
            addView(view, params);
            indicatorViews.add(view);
        }
        if (indicatorViews.size() > 0) {
            indicatorViews.get(0).setBackgroundResource(android.R.drawable.presence_online);
        }
    }

    /**
     * 设置选中页
     *
     * @param selected 页下标,从0开始
     */
    public void setSelectedPage(int selected) {
        for (int i = 0; i < indicatorViews.size(); i++) {
            if (i == selected) {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_online);
            } else {
                indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_invisible);
            }
        }
    }

}

 

DimensionConvert.java

用来转换dip和px的工具类

import android.content.Context;

/**
 * Created by shichaohui on 2015/7/10 0010.
 */
public class DimensionConvert {

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     *
     * @param context
     * @param dpValue 要转换的dp值
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     *
     * @param context
     * @param pxValue 要转换的px值
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}

 

MainActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private PageRecyclerView mRecyclerView = null;
    private List<String> dataList = null;
    private PageRecyclerView.PageAdapter myAdapter = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        initData();

        mRecyclerView = (PageRecyclerView) findViewById(R.id.cusom_swipe_view);
        // 设置指示器
        mRecyclerView.setIndicator((PageIndicatorView) findViewById(R.id.indicator));
        // 设置行数和列数
        mRecyclerView.setPageSize(3, 3);
        // 设置页间距
        mRecyclerView.setPageMargin(30);
        // 设置数据
        mRecyclerView.setAdapter(myAdapter = mRecyclerView.new PageAdapter(dataList, new PageRecyclerView.CallBack() {
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item, parent, false);
                return new MyHolder(view);
            }

            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                ((MyHolder)holder).tv.setText(dataList.get(position));
            }
        }));

    }

    private void initData() {
        dataList = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            dataList.add(String.valueOf(i));
        }
    }

    public class MyHolder extends RecyclerView.ViewHolder {

        public TextView tv = null;

        public MyHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.text);
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, getAdapterPosition() + "", Toast.LENGTH_SHORT).show();
                }
            });
            tv.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    myAdapter.remove(getAdapterPosition());
                    return true;
                }
            });
        }
    }  *后是两个布局文件:

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">

        <com.example.sch.myapplication.PageRecyclerView
            android:id="@+id/cusom_swipe_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <com.example.sch.myapplication.PageIndicatorView
            android:id="@+id/indicator"
            android:layout_width="match_parent"
            android:layout_marginBottom="20dp"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"/>

</LinearLayout>

 

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_margin="10dp"
        android:background="#770000ff"
        android:gravity="center" />

</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

END

GridView设置横向滚动

*近项目中需要使用到GridView,由于文字太长,内容多时竖直滚动GridView显得比较难看,于是找了一些资料发现可以实现GridView横向滚动。首先让GridView横向滚动需要HorizontalScrollView这个控件,例如:

main.xml布局:

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent” >

<HorizontalScrollView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:scrollbars=”none” >

<LinearLayout
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:orientation=”horizontal” >

<GridView
android:id=”@+id/gridview”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:layout_gravity=”center” >
</GridView>
</LinearLayout>
</HorizontalScrollView>

</LinearLayout>

<span style=”font-family:’Microsoft YaHei’;font-size:14px;”><LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent” >

<HorizontalScrollView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:scrollbars=”none” >

<LinearLayout
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:orientation=”horizontal” >

<GridView
android:id=”@+id/gridview”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:layout_gravity=”center” >
</GridView>
</LinearLayout>
</HorizontalScrollView>

</LinearLayout></span>
以上是主要布局,然后我们写GridView中每个item中的布局,比较简单,只是给大家介绍

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical” >

<ImageView
android:layout_width=”100dp”
android:layout_height=”100dp”
android:background=”#00ff00″ />

<TextView
android:id=”@+id/item_textview”
android:layout_width=”100dp”
android:layout_height=”20dp”
android:gravity=”center” />

</LinearLayout>

 

<span style=”font-family:’Microsoft YaHei’;font-size:14px;”><LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical” >

<ImageView
android:layout_width=”100dp”
android:layout_height=”100dp”
android:background=”#00ff00″ />

<TextView
android:id=”@+id/item_textview”
android:layout_width=”100dp”
android:layout_height=”20dp”
android:gravity=”center” />

</LinearLayout></span>

上面是item的布局,比较简单,就是一个ImageView和TextView,然后是主要实现GridView的Adapter。
以下是Activity的实现方法。

public class MainActivity extends Activity {

private GridView gridView;
private LayoutInflater inflater;
private List<String> dataList = new ArrayList<String>();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridView = (GridView) this.findViewById(R.id.gridview);
for (int i = 0; i < 10; i++) {
dataList.add(“测试” + i);
}
inflater = (LayoutInflater) this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
GridViewAdapter adapter = new GridViewAdapter();
gridView.setAdapter(adapter);
int size = dataList.size();
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
float density = dm.density;
int allWidth = (int) (110 * size * density);
int itemWidth = (int) (100 * density);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
allWidth, LinearLayout.LayoutParams.FILL_PARENT);
gridView.setLayoutParams(params);
gridView.setColumnWidth(itemWidth);
gridView.setHorizontalSpacing(10);
gridView.setStretchMode(GridView.NO_STRETCH);
gridView.setNumColumns(size);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

final class GridViewAdapter extends BaseAdapter {

@Override
public int getCount() {
return dataList.size();
}

@Override
public Object getItem(int position) {
return dataList.get(position);
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = inflater.inflate(R.layout.gridview_item, null);
TextView textView = (TextView) convertView
.findViewById(R.id.item_textview);
String str = dataList.get(position);
textView.setText(str);
return convertView;
}

}
}
<span style=”font-family:’Microsoft YaHei’;font-size:14px;”>public class MainActivity extends Activity {

private GridView gridView;
private LayoutInflater inflater;
private List<String> dataList = new ArrayList<String>();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gridView = (GridView) this.findViewById(R.id.gridview);
for (int i = 0; i < 10; i++) {
dataList.add(“测试” + i);
}
inflater = (LayoutInflater) this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
GridViewAdapter adapter = new GridViewAdapter();
gridView.setAdapter(adapter);
int size = dataList.size();
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
float density = dm.density;
int allWidth = (int) (110 * size * density);
int itemWidth = (int) (100 * density);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
allWidth, LinearLayout.LayoutParams.FILL_PARENT);
gridView.setLayoutParams(params);
gridView.setColumnWidth(itemWidth);
gridView.setHorizontalSpacing(10);
gridView.setStretchMode(GridView.NO_STRETCH);
gridView.setNumColumns(size);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

final class GridViewAdapter extends BaseAdapter {

@Override
public int getCount() {
return dataList.size();
}

@Override
public Object getItem(int position) {
return dataList.get(position);
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = inflater.inflate(R.layout.gridview_item, null);
TextView textView = (TextView) convertView
.findViewById(R.id.item_textview);
String str = dataList.get(position);
textView.setText(str);
return convertView;
}

}
}</span>
主要是设置GridView的LayoutParams,new 这个LayoutParams对象必须是这个View所在Layout的布局类型,然后设置GridView总长度和每个item的长度,然后设置总个数就可以实现横滚的效果!

Flutter开发之可滑动组件—GridView

一 概述
GridView是一个可滑动的视图组件,本文介绍GridView的常见创建方法

构造创建方式
快速创建方式:build,custom,count,extent
二 GridView
2.1 构造函数
GridView({
Key? key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController? controller,
bool? primary,
ScrollPhysics? physics,
bool shrinkWrap = false,
EdgeInsetsGeometry? padding,
required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double? cacheExtent,
List<Widget> children = const <Widget>[],
int? semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
Clip clipBehavior = Clip.hardEdge,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
String? restorationId,
})

2.2 常见属性说明

%title插图%num %title插图%num

三 构造方式创建GridView
3.1 代码
//State<MyHomePage>
ScrollController _gridViewController;

//State<MyHomePage>
@override
void initState() {
super.initState();
_gridViewController = ScrollController()
..addListener(() {
print(‘${_gridViewController.position}’);
});
}
//Widget build(BuildContext context)
GridView(
controller:_gridViewController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,crossAxisSpacing: 3, mainAxisSpacing: 3),
children: List.generate(50, (position) {
return Container(
alignment: Alignment.center,
height: 80,
color: Colors.primaries[position % Colors.primaries.length],
child: Text(“$position”),
);
}),
)

3.2 效果图

%title插图%num
3.3 ScrollController打印结果
I/flutter (16530): ScrollPositionWithSingleContext#9a250(offset: 72.8, range: 0.0..1746.0, viewport: 444.0, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics, DragScrollActivity#d503a(ScrollDragController#59958), ScrollDirection.reverse)
I/flutter (16530): ScrollPositionWithSingleContext#9a250(offset: 77.2, range: 0.0..1746.0, viewport: 444.0, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics, DragScrollActivity#d503a(ScrollDragController#59958), ScrollDirection.reverse)
I/flutter (16530): ScrollPositionWithSingleContext#9a250(offset: 83.2, range: 0.0..1746.0, viewport: 444.0, ScrollableState, ClampingScrollPhysics -> RangeMaintainingScrollPhysics, DragScrollActivity#d503a(ScrollDragController#59958), ScrollDirection.reverse)

四 快速创建方式:build,custom,count,extent
4.1 GridView.build
构建必须属性
itemBuilder是构建子控件
itemCount指定数据个数
代码
GridView.builder(
itemCount: 10,
controller:_gridViewController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,crossAxisSpacing: 3, mainAxisSpacing: 3),
itemBuilder: (context,index){
return Container(
alignment: Alignment.center,
height: 80,
color: Colors.primaries[index % Colors.primaries.length],
child: Text(“$index”),
);
},
)

4.2 GridView.custom
构建必须属性
childrenDelegate:提供子组件构建的代理,有SliverChildBuilderDelegate和SliverChildListDelegate可用于快速构建
快速构建代码
SliverChildBuilderDelegate方式

GridView.custom(
controller:_gridViewController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,crossAxisSpacing: 3, mainAxisSpacing: 3),
childrenDelegate: SliverChildBuilderDelegate((context,index){
return Container(
alignment: Alignment.center,
height: 80,
color: Colors.primaries[index % Colors.primaries.length],
child: Text(“$index”),
);
},childCount: 10) ,
)

SliverChildListDelegate方式

GridView.custom(
controller:_gridViewController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,crossAxisSpacing: 3, mainAxisSpacing: 3),
childrenDelegate: SliverChildListDelegate(List.generate(50, (position) {
return Container(
alignment: Alignment.center,
height: 80,
color: Colors.primaries[position % Colors.primaries.length],
child: Text(“$position”),
);})),
)

4.3 GridView.count
构建必须属性
crossAxisCount:交叉轴方向上个数

快速构建代码
GridView.count(
controller:_gridViewController,
crossAxisCount: 3,
children: List.generate(50, (position) {
return Container(
alignment: Alignment.center,
height: 80,
color: Colors.primaries[position % Colors.primaries.length],
child: Text(“$position”),
);}),
)

4.4 GridView.extent
构建必须属性
maxCrossAxisExtent:*大的横轴范围

构建代码
GridView.extent(
controller:_gridViewController,
maxCrossAxisExtent:100,
children: List.generate(50, (position) {
return Container(
alignment: Alignment.center,
height: 80,
color: Colors.primaries[position % Colors.primaries.length],
child: Text(“$position”),
);}),
)