ScrollView嵌套圆角Listview 实现同时滑动效果

免积分下载demo

效果图如下,我的模拟器异常,就看个静态图片把:
这里写图片描述这里写图片描述

首先看主布局文件activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.scrollview.MainActivity" >

    <com.example.scrollview.ElasticScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <com.example.scrollview.CornerListView
                android:id="@+id/contact_secret_listview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="10dip"
                android:background="@drawable/secret_contact_bg"
                android:cacheColorHint="#00000000"
                android:divider="@null"
                android:dividerHeight="0dp"
                android:scrollbars="none" />

        </LinearLayout>

    </com.example.scrollview.ElasticScrollView>

</RelativeLayout>

listview圆角secret_contact_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="@color/a" />

    <corners android:bottomLeftRadius="5dip"
        android:bottomRightRadius="5dip" android:topLeftRadius="5dip"
        android:topRightRadius="5dip" />

    <stroke android:color="@color/dividerColor" android:width="0.5dp"></stroke>
</shape>



 <color name="a">#ffffff</color>
 <color name="dividerColor">#cccccc</color>

MainActivity主界面代码

package com.example.scrollview;

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.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

    CornerListView cornerListView;
    private MyAdapter myAdapter;
    String[] datas = { "aaa", "bbb", "ccc", "ddd", "eee" };

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

        cornerListView = (CornerListView) findViewById(R.id.contact_secret_listview);

        // adapter
        myAdapter = new MyAdapter();

        cornerListView.setAdapter(myAdapter);
        setListViewHeightBasedOnChildren(cornerListView);

    }

    public void setListViewHeightBasedOnChildren(ListView listView) {
        // 获取ListView对应的Adapter
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            return;
        }

        int totalHeight = 0;
        for (int i = 0, len = listAdapter.getCount(); i < len; i++) {
            // listAdapter.getCount()返回数据项的数目
            View listItem = listAdapter.getView(i, null, listView);
            // 计算子项View 的宽高
            listItem.measure(0, 0);
            // 统计所有子项的总高度
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight
                + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        // listView.getDividerHeight()获取子项间分隔符占用的高度
        // params.height最后得到整个ListView完整显示需要的高度
        listView.setLayoutParams(params);
    }

    class MyAdapter extends BaseAdapter {

        LayoutInflater inflater;

        public MyAdapter() {
            inflater = LayoutInflater.from(MainActivity.this);
        }

        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return datas.length;
        }

        @Override
        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return datas[position];
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (convertView == null) {
                convertView = inflater.inflate(
                        R.layout.contact_secret_listview_item, parent, false);
                viewHolder = new ViewHolder(convertView);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            viewHolder.secretContactName.setText(datas[position]);
            return convertView;
        }

        class ViewHolder {
            private TextView secretContactName;

            ViewHolder(View view) {
                secretContactName = (TextView) view
                        .findViewById(R.id.secret_contact_name);
            }
        }

    }
}

ElasticScrollView

package com.example.scrollview;

    import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;

    /** 
     * ScrollView反弹效果的实现 
     */  
    public class ElasticScrollView extends ScrollView {  
        private View inner;// 子View  

        private float y;// 点击时y坐标  

        private Rect normal = new Rect();// 矩形(这里只是个形式,只是用于判断是否需要动画.)  

        private boolean isCount = false;// 是否开始计算  

        @SuppressLint("NewApi")  
        public ElasticScrollView(Context context, AttributeSet attrs) {  
            super(context, attrs);  
        }  

        /*** 
         * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate 
         * 方法,也应该调用父类的方法,使该方法得以执行. 
         */  


        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            if (getChildCount() > 0) {
                inner = getChildAt(0);
            }
        }

        /***
         * 监听touch 
         */  
        @Override  
        public boolean onTouchEvent(MotionEvent ev) {  
            if (inner != null) {  
                commOnTouchEvent(ev);  
            }  
            return super.onTouchEvent(ev);  
        }  

        /*** 
         * 触摸事件 
         *  
         * @param ev 
         */  
        public void commOnTouchEvent(MotionEvent ev) {  
            int action = ev.getAction();  
            switch (action) {  
            case MotionEvent.ACTION_DOWN:  
                break;  
            case MotionEvent.ACTION_UP:  
                // 手指松开.  
                if (isNeedAnimation()) {  
                    animation();  
                    isCount = false;  
                }  
                break;  
            /** 
             * 排除出第一次移动计算,因为第一次无法得知y坐标, 在MotionEvent.ACTION_DOWN中获取不到, 
             * 因为此时是MyScrollView的touch事件传递到到了LIstView的孩子item上面.所以从第二次计算开始. 
             * 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归0. 之后记录准确了就正常执行. 
             */  
            case MotionEvent.ACTION_MOVE:  
                final float preY = y;// 按下时的y坐标  
                float nowY = ev.getY();// 时时y坐标  
                int deltaY = (int) (preY - nowY);// 滑动距离  
                if (!isCount) {  
                    deltaY = 0; // 在这里要归0.  
                }  

                y = nowY;  
                // 当滚动到最上或者最下时就不会再滚动,这时移动布局  
                if (isNeedMove()) {  
                    // 初始化头部矩形  
                    if (normal.isEmpty()) {  
                        // 保存正常的布局位置  
                        normal.set(inner.getLeft(), inner.getTop(),inner.getRight(), inner.getBottom());  
                    }  
                    // 移动布局  
                    inner.layout(inner.getLeft(), inner.getTop() - deltaY / 2,inner.getRight(), inner.getBottom() - deltaY / 2);  
                }  
                isCount = true;  
                break;  

            default:  
                break;  
            }  
        }  

        /*** 
         * 回缩动画 
         */  
        public void animation() {  
            // 开启移动动画  
            TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(),normal.top);  
            ta.setDuration(200);  
            inner.startAnimation(ta);  
            // 设置回到正常的布局位置  
            inner.layout(normal.left, normal.top, normal.right, normal.bottom);  

            normal.setEmpty();  

        }  

        // 是否需要开启动画  
        public boolean isNeedAnimation() {  
            return !normal.isEmpty();  
        }  

        /*** 
         * 是否需要移动布局 inner.getMeasuredHeight():获取的是控件的总高度 
         *  
         * getHeight():获取的是屏幕的高度 
         *  
         * @return 
         */  
        public boolean isNeedMove() {  
            int offset = inner.getMeasuredHeight() - getHeight();  
            int scrollY = getScrollY();  
            // 0是顶部,后面那个是底部  
            if (scrollY == 0 || scrollY == offset) {  
                return true;  
            }  
            return false;  
        }  
    }  

CornerListView

package com.example.scrollview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ExpandableListView;
import android.widget.ListView;

public class CornerListView extends ListView implements Runnable {

    // 手指点位置的Y坐标
    private float mLastDownY = 0f;
    // 移动距离
    private int mDistance = 0;
    private int mStep = 0;
    // 是否移动过
    private boolean mPositive = false;

    /**
     * 构造器
     */
    public CornerListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

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

    /**
     * TouchEvent事件
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 系列事件,手指第一次按下时触发
                if (mLastDownY == 0f && mDistance == 0) {
                    mLastDownY = event.getY();
                    return true;
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                break;

            case MotionEvent.ACTION_UP:
                // 手指离开之后触发
                if (mDistance != 0) {
                    mStep = 1;
                    mPositive = (mDistance >= 0);
                    // 即可把你的Runnable对象增加到UI线程中运行。
                    this.post(this);
                    return true;
                }
                // 重新赋值
                mLastDownY = 0f;
                mDistance = 0;
                break;

            case MotionEvent.ACTION_MOVE: // 手指按下之后滑动触发
                if (mLastDownY != 0f) {
                    mDistance = (int) (mLastDownY - event.getY());
                    if ((mDistance < 0 && getFirstVisiblePosition() == 0 && getChildAt(
                            0).getTop() == 0)
                            || (mDistance > 0 && getLastVisiblePosition() == getCount() - 1)) {
                        // 第一个位置并且是想下拉,就滑动或者最后一个位置向上拉
                        // 这个判断的作用是在非顶端的部分不会有此滚动
                        mDistance /= 3; // 这里是为了减少滚动的距离
                        scrollTo(0, mDistance); // 滚动
                        return true;
                    }
                }
                // 置为0,有自动滑动的效果
                mDistance = 0;
                break;
        }
        return super.onTouchEvent(event);
    }

    public void run() {
        mDistance += mDistance > 0 ? -mStep : mStep;
        scrollTo(0, mDistance);
        // 下拉mPositive是false,上拉是true
        if ((mPositive && mDistance <= 0) || (!mPositive && mDistance >= 0)) {
            scrollTo(0, 0);
            mDistance = 0;
            mLastDownY = 0f;
            return;
        }
        mStep += 1;
        this.post(this);
    }
}

contact_secret_listview_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:orientation="vertical">
    <TextView
        android:id="@+id/secret_contact_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:text="name"
        android:layout_marginLeft="20dp" />

    <View
        android:layout_alignParentBottom="true"
        android:background="#cccccc"
        android:layout_marginLeft="14.333dp"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"></View>
</RelativeLayout>


说下需要注意的bug:
1、如果将来主布局文件改成如下的,listview直接套在scrollview下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.scrollview.MainActivity" >

    <com.example.scrollview.ElasticScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

         <com.example.scrollview.CornerListView
                android:id="@+id/contact_secret_listview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="10dip"
                android:background="@drawable/secret_contact_bg"
                android:cacheColorHint="#00000000"
                android:divider="@null"
                android:dividerHeight="0dp"
                android:scrollbars="none" />

    </com.example.scrollview.ElasticScrollView>

</RelativeLayout>

2、布局不变,但是在主页代码中setListViewHeightBasedOnChildren(cornerListView);这个方法去掉

以上1、2种情况都会遇到同样的一个bug,就是listview只显示一行,listview没有测量高度
在ScrollView中嵌套ListView空间,无法正确的计算ListView的大小,故可以通过代码,根据当前的ListView的列表项计算列表的尺寸。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值