美文网首页
2源码的角度分析View

2源码的角度分析View

作者: 帝乙岩 | 来源:发表于2017-09-04 10:54 被阅读0次

    同为纵向滑动冲突(核心代码)

    • 思路:根据业务逻辑处理,使用外部拦截
    • 首先有个父容器StickyLayout继承LinearLayout,内部放header和一个Listview;
    • 滑动规则:拦截:Header显示;listview滑动到顶部;Header隐藏,并且listview滑动手势为向下滑动;其他情况不拦截。
    • 先看下布局:
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.ceshiactivity.viewdemo.DemoCoreActivity">
    
        <com.example.ceshiactivity.viewdemo.StickyLayout
            android:id="@+id/sticky_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ffffff"
            android:orientation="vertical">
    
            <LinearLayout
                android:id="@+id/sticky_header"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:background="#78A524"
                android:gravity="center"
                android:orientation="vertical">
    
                <ImageView
                    android:id="@+id/imageView1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/ic_launcher" />
    
            </LinearLayout>
    
            <LinearLayout
                android:id="@+id/sticky_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
    
                <ListView
                    android:id="@+id/list"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="#fff4f7f9"
                    android:cacheColorHint="#00000000"
                    android:divider="#dddbdb"
                    android:dividerHeight="1.0px"
                    android:listSelector="@android:color/transparent" />
            </LinearLayout>
        </com.example.ceshiactivity.viewdemo.StickyLayout>
    </RelativeLayout>
    

    DemoCoreActivity
    这里主要调用一个接口,以获得listview的item0位置

    public class DemoCoreActivity extends AppCompatActivity implements StickyLayout.OnGiveUpTouchEventListener {
        private StickyLayout stickyLayout;
        ListView listView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_demo_core);
            initView();
            LinearLayout ll= (LinearLayout) findViewById(R.id.sticky_header);
            stickyLayout= (StickyLayout) findViewById(R.id.sticky_layout);
            stickyLayout.setOnGiveUpTouchEventListener(this);
    
        }
    
        private void initView() {
            listView = (ListView)findViewById(R.id.list);
            ArrayList<String> datas = new ArrayList<String>();
            for (int i = 0; i < 50; i++) {
                datas.add("name " + i);
            }
    
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                    R.layout.content_list_item, R.id.name, datas);
            listView.setAdapter(adapter);
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view,
                                        int position, long id) {
                    Toast.makeText(DemoCoreActivity.this, "click item",
                            Toast.LENGTH_SHORT).show();
    
                }
            });
        }
    
        @Override
        public boolean giveUpTouchEvent(MotionEvent event) {
            //如果位置为0,获取0位置的view
            if (listView.getFirstVisiblePosition() == 0) {
                View view = listView.getChildAt(0);
                //view在顶部位置单位像素
                if (view != null && view.getTop() >= 0) {
                    return true;
                }
            }
            return false;
        }
    

    然后是核心代码StickyLayout

    public class StickyLayout extends LinearLayout {
        private static final String TAG = "StickyLayout";
        private static final boolean DEBUG = true;
        //用来判断listview是否滑动到顶端
        public interface OnGiveUpTouchEventListener {
            public boolean giveUpTouchEvent(MotionEvent event);
        }
    
        private View mHeader;//上半部分布局
        private View mContent;//下半部分布局
        private OnGiveUpTouchEventListener mGiveUpTouchEventListener;
    
        // header的高度  单位:px
        private int mOriginalHeaderHeight;
        private int mHeaderHeight;
        //滑动方向
        private int mStatus = STATUS_EXPANDED;
        public static final int STATUS_EXPANDED = 1;
        public static final int STATUS_COLLAPSED = 2;
        //滚动的像素值
        private int mTouchSlop;
    
        // 分别记录上次滑动的坐标
        private int mLastX = 0;
        private int mLastY = 0;
    
        // 分别记录上次滑动的坐标(onInterceptTouchEvent)
        private int mLastXIntercept = 0;
        private int mLastYIntercept = 0;
    
        // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2
        private static final int TAN = 2;
    
        private boolean mIsSticky = true;
        private boolean mInitDataSucceed = false;
        private boolean mDisallowInterceptTouchEventOnHeader = true;
    
        public StickyLayout(Context context) {
            super(context);
        }
    
        public StickyLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        public StickyLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        public void onWindowFocusChanged(boolean hasWindowFocus) {
            super.onWindowFocusChanged(hasWindowFocus);
            if (hasWindowFocus && (mHeader == null || mContent == null)) {
                initData();
            }
        }
    
        private void initData() {
            int headerId= getResources().getIdentifier("sticky_header", "id", getContext().getPackageName());
            int contentId = getResources().getIdentifier("sticky_content", "id", getContext().getPackageName());
            if (headerId != 0 && contentId != 0) {
                mHeader = findViewById(headerId);
                mContent = findViewById(contentId);
                mOriginalHeaderHeight = mHeader.getMeasuredHeight();
                mHeaderHeight = mOriginalHeaderHeight;
                mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
                if (mHeaderHeight > 0) {
                    mInitDataSucceed = true;
                }
                if (DEBUG) {
                    Log.d(TAG, "mTouchSlop = " + mTouchSlop + "mHeaderHeight = " + mHeaderHeight);
                }
            } else {
                throw new NoSuchElementException("Did your view with id \"sticky_header\" or \"sticky_content\" exists?");
            }
        }
    
        /**
         * 传递跟布局
         * @param l this
         */
        public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {
            mGiveUpTouchEventListener = l;
        }
    
        /**
         * 片段
         */
        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            int intercepted = 0;
            int x = (int) event.getX();
            int y = (int) event.getY();
    
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    mLastXIntercept = x;
                    mLastYIntercept = y;
                    mLastX = x;
                    mLastY = y;
                    intercepted = 0;
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastXIntercept;
                    int deltaY = y - mLastYIntercept;
                    if (mDisallowInterceptTouchEventOnHeader && y <= getHeaderHeight()) {
                        //竖直滑动距离小于header高度,不拦截
                        intercepted = 0;
                    } else if (Math.abs(deltaY) <= Math.abs(deltaX)) {
                        //竖直距离差小于等于水平距离不拦截
                        intercepted = 0;
                    } else if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {
                        //header展开状态,向上滑动,拦截
                        intercepted = 1;
                    } else if (mGiveUpTouchEventListener != null) {
                        //布局不为空
                        if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) {
                            //listview滑动到顶部并向下滑动,拦截
                            intercepted = 1;
                        }
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    intercepted = 0;
                    mLastXIntercept = mLastYIntercept = 0;
                    break;
                }
                default:
                    break;
            }
    
            if (DEBUG) {
                Log.d(TAG, "intercepted=" + intercepted);
            }
            return intercepted != 0 && mIsSticky;
        }
        /**
         * 片段
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (!mIsSticky) {
                return true;
            }
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastX;
                    int deltaY = y - mLastY;
                    if (DEBUG) {
                        Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + "  deltaY=" + deltaY + "  mlastY=" + mLastY);
                    }
                    mHeaderHeight += deltaY;
                    setHeaderHeight(mHeaderHeight);
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置
                    int destHeight = 0;
                    if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {
                        destHeight = 0;
                        mStatus = STATUS_COLLAPSED;
                    } else {
                        destHeight = mOriginalHeaderHeight;
                        mStatus = STATUS_EXPANDED;
                    }
                    // 慢慢滑向终点
                    this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
                    break;
                }
                default:
                    break;
            }
            mLastX = x;
            mLastY = y;
            return true;
        }
    
        public void smoothSetHeaderHeight(final int from, final int to, long duration) {
            smoothSetHeaderHeight(from, to, duration, false);
        }
    
        /**
         * 线程设置高度变化
         * @param from head初始高度
         * @param to  移动到的高度
         * @param duration  时间
         * @param modifyOriginalHeaderHeight
         */
        public void smoothSetHeaderHeight(final int from, final int to, long duration, final boolean modifyOriginalHeaderHeight) {
            final int frameCount = (int) (duration / 1000f * 30) + 1;
            //速度公式
            final float partation = (to - from) / (float) frameCount;
            new Thread("Thread#smoothSetHeaderHeight") {
    
                @Override
                public void run() {
                    for (int i = 0; i < frameCount; i++) {
                        //动态变化
                        final int height;
                        if (i == frameCount - 1) {
                            height = to;
                        } else {
                            height = (int) (from + partation * i);
                        }
                        post(new Runnable() {
                            public void run() {
                                setHeaderHeight(height);
                            }
                        });
                        try {
                            sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    if (modifyOriginalHeaderHeight) {
                        setOriginalHeaderHeight(to);
                    }
                };
    
            }.start();
        }
    
        public void setOriginalHeaderHeight(int originalHeaderHeight) {
            mOriginalHeaderHeight = originalHeaderHeight;
        }
    
        public void setHeaderHeight(int height, boolean modifyOriginalHeaderHeight) {
            if (modifyOriginalHeaderHeight) {
                setOriginalHeaderHeight(height);
            }
            setHeaderHeight(height);
        }
    
        /**
         * 赋值 并设置高度到Header中
         * @param height
         */
        public void setHeaderHeight(int height) {
            if (!mInitDataSucceed) {
                initData();
            }
    
            if (DEBUG) {
                Log.d(TAG, "setHeaderHeight height=" + height);
            }
            if (height <= 0) {
                height = 0;
            } else if (height > mOriginalHeaderHeight) {
                //这行注释掉可实现下拉到任意位置后弹回
                height = mOriginalHeaderHeight;
            }
    
            if (height == 0) {
                mStatus = STATUS_COLLAPSED;
            } else {
                mStatus = STATUS_EXPANDED;
            }
    
            if (mHeader != null && mHeader.getLayoutParams() != null) {
                mHeader.getLayoutParams().height = height;
                mHeader.requestLayout();
                mHeaderHeight = height;
            } else {
                if (DEBUG) {
                    Log.e(TAG, "null LayoutParams when setHeaderHeight");
                }
            }
        }
    
        public int getHeaderHeight() {
            return mHeaderHeight;
        }
    
        public void setSticky(boolean isSticky) {
            mIsSticky = isSticky;
        }
    
        public void requestDisallowInterceptTouchEventOnHeader(boolean disallowIntercept) {
            mDisallowInterceptTouchEventOnHeader = disallowIntercept;
        }
    }
    

    效果图:

    纵向与纵向滑动冲突.jpg

    遗留问题手指向上滑动不离开屏幕则listview无法滑动(待解决)

    相关文章

      网友评论

          本文标题:2源码的角度分析View

          本文链接:https://www.haomeiwen.com/subject/plohhxtx.html