scroller与scrollto使用(二)

本文介绍了一个自定义的水平ScrollView控件,能够实现无限滑动且带有加速功能。通过复用七个子视图来避免内存溢出,详细展示了控件的设计思路、触摸事件处理及滑动计算方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求是这样的,一个类似于水平scrollview的控件左右可以无限滑动,还有加速滑动的控件,

这样如果在viewgroup中无限添加view的话就会oom。所以我用了7个子控件不断的复用来实现。

在第一个子控件滑过去的时候scrollto(-childrenwidth,0)来实现。


    public void init() {
        setOrientation(HORIZONTAL);
        scroller = new Scroller(getContext());
        ViewConfiguration configuration = ViewConfiguration.get(getContext());
        // 获取TouchSlop值
        mTouchSlop = configuration.getScaledPagingTouchSlop();
        //f设置最小速率
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        // 设置最大速率
        float density = getContext().getResources().getDisplayMetrics().density;
        mMaximumVelocity = (int) (4000 * 0.5f * density);
    }
先初始化一些最大加速度,最小加速度以免滑动速度过大跳过判断数值。

 List<Long> list=new ArrayList<>();

储存子控件的值在滑动时fling时动态修改

  @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //添加子控件
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);

        int width = wm.getDefaultDisplay().getWidth();
        int height = wm.getDefaultDisplay().getHeight();
        int childheight = height / 7;
        int childwidth = (width + 200) / 7;
        Date curDate    =   new Date(System.currentTimeMillis());
        long a= DateUtils.format(curDate);
        for (int i = 0; i < 7; i++) {
            ItemView tv = new ItemView(getContext());
            tv.setdate(a-i-3);
            list.add(a-i-3);
            if (i  == 3) {
                tv.setBackgroundColor(Color.BLUE);
            }
            tv.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
//                    Log.e(TAG, "onClick: "+((TextView) v).getText() );
                }
            });
            tv.setLayoutParams(new ViewGroup.LayoutParams(childwidth, childheight));
            addView(tv);
        }
        //刚开始移动到中点
        Log.e(TAG, "onFinishInflate: "+getChildAt(0).getWidth()/2 );
        scrollTo(childwidth/2,0);
        currentposition =childwidth/2;
       // getScrollX();
    }
父布局完成后添加子控件的值,这时候获取viewgroup控件大小为0子控件也是0,可以根据需求来写,

或者换个可以获取width的地方如ondrawonmeasure

    switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //是否正在滚动,滚动的话则移除滚动事件
                if (CURRENTMODE==MOTIONSTATE.ACTIONFLING){
                    CURRENTMODE=MOTIONSTATE.ACTIONEND;
                    scroller.forceFinished(true);
                }
                //记录按下的位置给后面move方法参考
                downposition = (int) ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                mXMove = ev.getRawX();
                float diff = Math.abs(mXMove - downposition);

                // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
                if (diff > mTouchSlop) {
                    return true;
                }
按下去时获取值移动时计算值并拦截事件向下传递。

  @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (tracker == null) {
            tracker = VelocityTracker.obtain();
        }
        //获取手指滑动的加速度
        tracker.addMovement(event);

        switch (event.getAction()) {

            case MotionEvent.ACTION_MOVE:
                //触发computeScroll标记为move的事件在computeScroll中触发更改方法
                CURRENTMODE=MOTIONSTATE.ACTIONMOVE;
                //移动时手指的x坐标
                moveposition = (int) event.getX();

                Log.e(TAG, "onTouchEvent: move" + moveposition);
                //手指移动的距离
                movedistance = moveposition - downposition;
                Log.e( "onTouchEventdis: ", movedistance+"");
                if (movedistance<0){
                    //手指向左移动
                    MOVEDIRECTION=MOTIONSTATE.LEFT;
                }else{
                    //手指向右滑动
                    MOVEDIRECTION=MOTIONSTATE.RIGHT;

                }
                Log.e("onTouchEvent: ",currentposition+"");
                //需要复用view
                //currentposition是上次scroll的距离在computescroll中处理移动方法
                invalidate();

                break;
            case MotionEvent.ACTION_UP:
                clearele();
                CURRENTMODE=MOTIONSTATE.ACTIONUP;
                currentposition=getScrollX();
                // currentposition -= position;
                tracker.computeCurrentVelocity(1000);
                float velocity=tracker.getXVelocity();
                //log显示手指向左3滑动负数向右滑动正数,min200max8000
                Log.e("tracker.getXVelocity()", mMinimumVelocity+"");
                //小于最小速率不滚动
                if (Math.abs(velocity)<Math.abs(mMinimumVelocity)){

                }else {
                    //向左滑动
                    if (velocity<0){
                        //取较大的数
                        velocity=Math.max(velocity,-mMaximumVelocity);
                        MOVEDIRECTION=MOTIONSTATE.LEFT;
                    }else if (velocity>0){
                        //取较小的数
                        velocity=Math.min(velocity,mMaximumVelocity);
                        MOVEDIRECTION=MOTIONSTATE.RIGHT;
                    }
                    //从当前位置开始按速度增加/减少scroller的值
                    CURRENTMODE=MOTIONSTATE.ACTIONFLING;
                    //启动fling
                    scroller.fling(currentposition, 0, ((int) - velocity), 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
                    invalidate();
                }

                break;
        }

        return true;

    }
滑动时记录当前标记,方便在computescroll方法中计算;

里面都有注释就不说了。

这是一些状态常量用来标记控件

    class MOTIONSTATE{
        static final int ACTIONDOWN=1;
        static final int ACTIONMOVE=2;
        static final int ACTIONUP=3;
        static final int ACTIONFLING=4;
        static final int ACTIONEND=5;
        static final int LEFT =-1;
        static final int RIGHT=1;
    }


下面分成了两类进行滚动的计算:

1.move2.fling

  @Override
    public void computeScroll() {
        //处理移动
        if (CURRENTMODE==MOTIONSTATE.ACTIONMOVE){
            if (MOVEDIRECTION == MOTIONSTATE.RIGHT) {
                movedistance += getChildAt(0).getWidth();
            }
            movedir = (currentposition -movedistance)%getChildAt(0).getWidth();
            movedir1 =movedir%getChildAt(0).getWidth();

            moveele = ( currentposition -movedistance)/getChildAt(0).getWidth();
           // movedirnum是上次的值moveele是这次的值,值改变时change
            Log.e( "computeScroll: ",moveele+","+movedirnum);
            if (moveele!=movedirnum){
                //int moveele = movedirnum - movenum;
             //  movedirnum
               /* if (MOVEDIRECTION == MOTIONSTATE.LEFT) {
                    //向左移动则位置元素都要向右移动一位
                  moveElement(moveele-movedirnum);
                } else if (MOVEDIRECTION == MOTIONSTATE.RIGHT) {*/
                    //前面处理过数据在数据集合里面处理这些数据所以方法一样,这里写ifelse方便查看逻辑

                    moveElement(moveele-movedirnum);
               // }
           }
            movedirnum =(currentposition -movedistance)/getChildAt(0).getWidth();
           // movenum =movedirnum;
            //设定范围,移出则回来
            if (MOVEDIRECTION == MOTIONSTATE.RIGHT) {
                //向右移动时多加一个width
                movedir+=getChildAt(0).getWidth();

            }
            if (movedir>getChildAt(0).getWidth()){
                movedir-=getChildAt(0).getWidth();
            }
            Log.e("computemovedir: " ,movedir+"");
            Log.e("computemove: " ,getChildAt(0).getWidth()+"");
            Log.e("computemovemode: " ,MOVEDIRECTION+"");
                scrollTo(movedir, scroller.getCurrY());
        }

        //是否完成计算处理fling模式
        if (CURRENTMODE==MOTIONSTATE.ACTIONFLING) {
            if (scroller.computeScrollOffset()) {
                //设置scrollto 的位置
                int scrollposition = scroller.getCurrX() % getChildAt(0).getWidth();
                int curentnum = scroller.getCurrX() / getChildAt(0).getWidth();
                if (scrollnum != curentnum) {
                    //当scrollnum不相等的时候移动element
                    int moveelementnum = curentnum - scrollnum;
                    if (MOVEDIRECTION == MOTIONSTATE.LEFT) {
                        //向左移动则位置元素都要向右移动一位
                        moveElement(moveelementnum);
                    } else if (MOVEDIRECTION == MOTIONSTATE.RIGHT) {
                        //前面处理过数据在数据集合里面处理这些数据所以方法一样,这里写ifelse方便查看逻辑
                        moveElement(moveelementnum);
                    }
                }
                if (MOVEDIRECTION == MOTIONSTATE.RIGHT) {
                    //前面处理过数据在数据集合里面处理这些数据所以方法一样,这里写ifelse方便查看逻辑
                    scrollposition+= getChildAt(0).getWidth();
                }
                //记录当前的数目下次判断用
                scrollnum = curentnum;
                //移动画布
                scrollTo(scrollposition, scroller.getCurrY());
            } else {
                scrollnum = 0;
                //记录当前scroll值
                currentposition = getScrollX();
                CURRENTMODE=MOTIONSTATE.ACTIONEND;
               // CURRENTMODE=
            }
        }
        if (CURRENTMODE==MOTIONSTATE.ACTIONEND ||CURRENTMODE==MOTIONSTATE.ACTIONUP){
            scrollTo(getChildAt(0).getWidth()/2,0);
        }
    }
 int curentnum = scroller.getCurrX() / getChildAt(0).getWidth();
这个方法是计算当前滑动出屏幕多少个子控件

假如当前滑动的数目与上次滑动过去的子控件数目不相同时则更改数值‘

如果有更好的办法标记我也不想这么写很low’

 movedir = (currentposition -movedistance)%getChildAt(0).getWidth();
这个方法求模,计算当前需要滑到什么位置,假如你向左向右滑动了1个childrenwidth+20

实际你只需要将7个view拉回来放在20的位置然后更改控件的内容效果是一样的。


    //移动element的方法。调用viewchild的移动方法
    public void moveElement(int a){

        List<Long>list1=new ArrayList<>();
        list1.clear();
        for (int i = 0; i < list.size(); i++) {
            list1.add(i,list.get(i)-a);
        }
        list.clear();
        list.addAll(list1);
        for (int i = 0; i < list.size(); i++) {
            //list1.add(i,list.get(i)+a);
            ((ItemView) getChildAt(i)).setdate(list.get(i));
        }
    }
上面是更改控件内容的方法。
a为-1则是向左移动a为+1则为向右移动。

由于scrollto是移动的画布,控件的属性都是不变的所以非常难控制当前view移动到了什么位置。

其实难点在于控制子view滑到了什么位置。

下面贴一下子控件的自定义,由于是单个的view没什么难点,我这个代码也只是测试用的,写的简单了点


public class ItemView extends View {
    private String date;
    private Paint paint1;

    public ItemView(Context context) {
        super(context);
        init();
    }

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

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

    public void setdate(long i){
           // String date1=i/10000+"";
           // String date2=(i%10000)/100+"";
            String date3=i%100+"";
            date=date3;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(date,0,getHeight()/2,paint1);
//canvas.drawText();
    }
    public  void init(){
        paint1 = new Paint();
        paint1.setColor(Color.BLACK);
        paint1.setStrokeWidth(10);
        paint1.setAntiAlias(true);
        paint1.setStyle(Paint.Style.STROKE);
        date = "";
       //paint1.setTextAlign(Paint.Align.CENTER);
        //paint1.setTextAlign();
        paint1.setTextSize(100);
    }
然后是日期转换的类:

public class DateUtils {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

    public static long format (Date curDate){
        Log.e("format: ",sdf.format(curDate) );

        return  Long.valueOf(sdf.format(curDate));
    }

}

能写出来其实很满足的,卡在move方法很久找不到好的方法控制移动,而fling只要控制scroller就可以更加方便。

百度网盘下载代码:

http://pan.baidu.com/s/1kUXncOn





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值