精华内容
下载资源
问答
  • 今天在导入一个Android Studio 工程时,发生了这样的错误: Error: Library projects cannot set applicationId.... default config 网上找到的解决方法分享一下,出现这个错误的原因在于工程下有个库,库的

    今天在导入一个Android Studio 工程时,发生了这样的错误:

    Error: Library projects cannot set applicationId. applicationId is set to "com.du.android.recyclerview" in default config

    网上找到的解决方法分享一下,出现这个错误的原因在于工程下有个库,库的build.gradle里面defaultConfig内存在:

        defaultConfig {
             applicationId "com.du.android.recyclerview"
            minSdkVersion 21
            targetSdkVersion 21
            versionCode 1
            versionName "1.0"

        }

    红色部分的字体,这时删除红色标注的内容即可!

    展开全文
  • 还在用ListView?

    万次阅读 多人点赞 2016-04-08 16:24:31
    还在用Lisview?RecyclerView都已经出来一年多了! 想必大家多或多或少的接触过或者了解过RecyclerView,为什么没有用起来,原因大概如下? ListView我用的挺好的,为什么要换RecyclerView?... RecyclerVi

    还在用Lisview?RecyclerView都已经出来一年多了!

    想必大家多或多或少的接触过或者了解过RecyclerView,为什么没有用起来,原因大概如下?

    • ListView我用的挺好的,为什么要换RecyclerView?
    • ListView稳定,熟悉,还知道很多开源库,特别的好用!
    • RecyclerView不能添加头部,ListView能!

    RecyclerView

    RecyclerView最大的优势就是灵活,RecyclerView只需改变一行代码就可以变化多种不同的布局显示排版,这一点对于开发者是非常方便的!

    还有RecyclerView.Adapter,比BaseAdapter做了更好的封装,把BaseAdapter的getView方法拆分成onCreateViewHolder方法和onBindViewHolder方法,强制需要创建ViewHolder,这样的好处就是避免了初学者写性能不佳的代码

    在Andorid 5.0出来不久,我就已经写过RecyclerView的简单介绍以及基本使用,不了解的可以看看ListView升级版RecyclerView,了解过的同学可以忽略,并往下看。

    在实战中我们会遇到什么问题?

    get到下面的技能就能够在使用RcyclerView的大路上畅通无阻了!

    • 添加分割线
    • 添加点按效果
    • 列表动画
    • 改变某个数据保持当前位置
    • 添加头部尾部
    • 列表分组
    • 各种效果集成Demo
    • 更灵活的RecyclerView

    添加分割线

    //通过以下方法添加分割线
    mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
    DividerItemDecoration.VERTICAL_LIST));

    需要RecyclerView.ItemDecoration这个抽象类实现一些方法
    (后续有更偷懒的方法)

    /**
     * This class is from the v7 samples of the Android SDK. It's not by me!
     * <p/>
     * See the license above for details.
     */
    public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    
        private static final int[] ATTRS = new int[]{
                android.R.attr.listDivider
        };
    
        public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    
        public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    
        private Drawable mDivider;
    
        private int mOrientation;
    
        public DividerItemDecoration(Context context, int orientation) {
            final TypedArray a = context.obtainStyledAttributes(ATTRS);
            mDivider = a.getDrawable(0);
            a.recycle();
            setOrientation(orientation);
        }
    
        public void setOrientation(int orientation) {
            if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
                throw new IllegalArgumentException("invalid orientation");
            }
            mOrientation = orientation;
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent) {
            Log.v("recyclerview - itemdecoration", "onDraw()");
    
            if (mOrientation == VERTICAL_LIST) {
                drawVertical(c, parent);
            } else {
                drawHorizontal(c, parent);
            }
    
        }
    
    
        public void drawVertical(Canvas c, RecyclerView parent) {
            final int left = parent.getPaddingLeft();
            final int right = parent.getWidth() - parent.getPaddingRight();
    
            final int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                        .getLayoutParams();
                final int top = child.getBottom() + params.bottomMargin;
                final int bottom = top + mDivider.getIntrinsicHeight();
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    
        public void drawHorizontal(Canvas c, RecyclerView parent) {
            final int top = parent.getPaddingTop();
            final int bottom = parent.getHeight() - parent.getPaddingBottom();
    
            final int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                        .getLayoutParams();
                final int left = child.getRight() + params.rightMargin;
                final int right = left + mDivider.getIntrinsicHeight();
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    
        @Override
        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
            if (mOrientation == VERTICAL_LIST) {
                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
            } else {
                outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
            }
        }
    }

    看到这里应该有同学吐槽了,就添加一根线至于吗?还需要重写方法,ListView可是只需要简单配置一下就好了。

       android:divider="@color/grey"
       android:dividerHeight="5dp"

    为此我也感觉太麻烦,于是我想了一个办法:
    直接在item_view里面底部自己添加一根线布局,这样就无需重写了,并且这样还有个好处就是,如果细心的同学会发现,添加分割线,最后一个item下面不会有分割线,显然当数据量不足一个屏幕的时候显得很突兀,但是在item_view下面添加一个线的布局则不会出现这种情况

    添加点按效果

    RecyclerView直接在item_view里面配置即可

    动画

    一个好的用户体验就是要有操作动画的过渡,而不是生硬的刷新列表。

    推荐一个RecyclerView的动画库(recyclerview-animators
    这里写图片描述

    RecyclerView自带添加、删除动画,而ListView则需添加额外的代码才能实现。

    • 删除调用RecyclerView的adapter的notifyItemRemoved
    • 添加调用RecyclerView的adapter的notifyItemInserted

    说到adapter我们就来说说RecyclerView.Adapter和BaseAdapter相比,额外提供了一下这些方法:

    // 数据发生了改变,那调用这个方法,传入改变对象的位置。
    public final void notifyItemChanged(int position);
    
    // 可以刷新从positionStart开始itemCount数量的item了
    public final void notifyItemRangeChanged(int positionStart, int itemCount);
    
    // 添加,传入对象的位置。
    public final void notifyItemInserted(int position);
    
    // 删除,传入对象的位置。
    public final void notifyItemRemoved(int position);
    
    // 对象从fromPosition移动到toPosition
     public final void notifyItemMoved(int fromPosition, int toPosition); 
    
    //批量添加 
    public final void notifyItemRangeInserted(int positionStart, int itemCount);
    
    //批量删除
    public final void notifyItemRangeRemoved(int positionStart, int itemCount);

    改变列表某个布局状态且保持当前位置

    这种需求是普遍存在的,就是改变列表某一个item数据,然后刷新列表,如果是ListView刷新后则会回到最顶部,而RecyclerView同样的操作但是原来滑动的位置不变。

    各种解决方案的RecyclerView的Adapter

    BaseRecyclerViewAdapterHelper

    效果展示

    更灵活的RecyclerView

    twoway-view
    封装了RecyclerView常用方法,如click等等,以及支持了更多不同的布局,使得RecyclerView使用起来更简单!
    这里写图片描述

    造起来!小伙伴们!

    展开全文
  • 给RecyclerView最纯粹的下拉刷新和上拉加载更多

    万次阅读 热门讨论 2016-08-16 23:11:37
    RecyclerView 出现以后,Android 里的下拉刷新和加载更多实现起来就非常容易了。当然,现成的库也有很多,只是总会有不一样的需求,而且我们往往...总结起来,无非两点,一是用 SwipeRefreshLayout 包裹 RecyclerVi

    RecyclerView 出现以后,Android 里的下拉刷新和加载更多实现起来就非常容易了。当然,现成的库也有很多,只是总会有不一样的需求,而且我们往往只需要最基本的下拉刷新和加载更多功能,而不需要其他多余的功能。我只需要一个最纯粹的下拉刷新和加载更多。所以,自己动手显然是最好的结果了,也算是个小练习。总结起来,无非两点,一是用 SwipeRefreshLayout 包裹 RecyclerView 实现下拉刷新,二是滑倒底部的时候自动加载实现加载更多。

    需要注意的是两点:

    1. 下拉刷新是通过实现 SwipeRefreshLayout.OnRefreshListener 接口来实现的,也就是说下拉刷新具有了通用性,不只是 RecyclerView ;
    2. 加载更多要通过 LinearLayoutManager 来获取 RecyclerView 是否滑动到底部来实现。

    这样我们就把问题分解了。第一步,我们先看最简单的下拉刷新,由于代码过于简单,我就直接贴代码了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
        swipeRefreshLayout.setColorSchemeResources(
                R.color.google_blue,
                R.color.google_green,
                R.color.google_red,
                R.color.google_yellow
        );
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(linearLayoutManager);
        setData();
        RefreshAdapter refreshAdapter = new RefreshAdapter(mList, this);
        recyclerView.setAdapter(refreshAdapter);
        swipeRefreshLayout.setOnRefreshListener(this);
    }
    
    
    private void setData() {
        mList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mList.add("第" + i + "个");
        }
    }
    
    @Override
    public void onRefresh() {
        Observable
                .timer(2, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
                .map(new Func1<Long, Object>() {
                    @Override
                    public Object call(Long aLong) {
                        fetchingNewData();
                        swipeRefreshLayout.setRefreshing(false);
                        refreshAdapter.notifyDataSetChanged();
                        Toast.makeText(MainActivity.this, "Refresh Finished!", Toast.LENGTH_SHORT).show();
                        return null;
                    }
                }).subscribe();
    }
    
    private void fetchingNewData() {
        mList.add(0, "下拉刷新出来的数据");
    }
    

    然后就是加载更多功能,RecyclerView 有一个方法 addOnScrollListener ,我们只要传入一个RecyclerView.OnScrollListener 就可以实现加载更多了,但是事实是为了充分保证 RecyclerView 的灵活性,Android 本身是没有对这个滑动接口做处理的,需要我们自定义个加载更多的接口去实现它,然后才能真正实现加载更多。实现起来也很简单,我们只要重写 onScrolled 方法即可。下面是一个封装好的加载更多的接口实现类,然后作为参数传进去就好了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    
    public abstract class EndlessRecyclerOnScrollListener extends
            RecyclerView.OnScrollListener {
    
        private int previousTotal = 0;
        private boolean loading = true;
        int firstVisibleItem, visibleItemCount, totalItemCount;
    
        private int currentPage = 1;
    
        private LinearLayoutManager mLinearLayoutManager;
    
        public EndlessRecyclerOnScrollListener(
                LinearLayoutManager linearLayoutManager) {
            this.mLinearLayoutManager = linearLayoutManager;
        }
    
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
    
            visibleItemCount = recyclerView.getChildCount();
            totalItemCount = mLinearLayoutManager.getItemCount();
            firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
    
            if (loading) {
                if (totalItemCount > previousTotal) {
                    loading = false;
                    previousTotal = totalItemCount;
                }
            }
            if (!loading
                    && (totalItemCount - visibleItemCount) <= firstVisibleItem) {
                currentPage++;
                onLoadMore(currentPage);
                loading = true;
            }
        }
    
        public abstract void onLoadMore(int currentPage);
    }
    

    然后就是具体的加载更多的实现逻辑了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(linearLayoutManager) {
        @Override
        public void onLoadMore(int currentPage) {
            simulateLoadMoreData();
        }
    });
    
    private void simulateLoadMoreData() {
        Observable
                .timer(2, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
                .map(new Func1<Long, Object>() {
                    @Override
                    public Object call(Long aLong) {
                        loadMoreData();
                        stringAdapter.notifyDataSetChanged();
                        Toast.makeText(MainActivity.this, "Load Finished!", Toast.LENGTH_SHORT).show();
                        return null;
                    }
                }).subscribe();
    }
    
    private void loadMoreData() {
        List<String> moreList = new ArrayList<>();
        for (int i = 10; i < 13; i++) {
            moreList.add("加载更多的数据");
        }
        mList.addAll(moreList);
    }
    

    这样,下拉刷新和加载更多就实现了,就这么简单。

    当然,如果你想要一个加载更多的 ProgressBar 展示的话,可以给 RecyclerView 添加一个 Footer ,用来展示加载更多的过程。GitHub 上搜索一下会有很多实现方案,这里我随便贴一个作为例子。HeaderViewRecyclerAdapter,一个用起来相当方便的 Gist ,如果用在上面的例子里就会是这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    HeaderViewRecyclerAdapter stringAdapter = new HeaderViewRecyclerAdapter(refreshAdapter);
    recyclerView.setAdapter(stringAdapter);
    createLoadMoreView();
    
    private void createLoadMoreView() {
        View loadMoreView = LayoutInflater
                .from(MainActivity.this)
                .inflate(R.layout.view_load_more, recyclerView, false);
        stringAdapter.addFooterView(loadMoreView);
    }
    

    然后把对应的 adapter 替换以后即可。代码看这里SwipeRefreshRecyclerView

    我们可以看到运行效果非常完美


    展开全文
  • 最近研究Android list(譬如RecyclerView)下拉动效(譬如下拉刷新)的实现方式。看到一个不错的开源项目,实现类似iPhone上的Chrome下拉更多操作。详细研究了实现方式,记录如下。...以RecyclerVi

    最近研究Android list(譬如RecyclerView)下拉动效(譬如下拉刷新)的实现方式。看到一个不错的开源项目,实现类似iPhone上的Chrome下拉更多操作。详细研究了实现方式,记录如下。

    1.开源代码地址

    2.如何使用
    以RecyclerView作为list为例,其他情况类似
    (1)Layout:
    在RecyclerView外层套一层Layout-ChromeLikeSwipeLayout:
    <com.asha.ChromeLikeSwipeLayout
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:id="@+id/chrome_like_swipe_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.RecyclerView
            android:background="#fff5d0"
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >
        </android.support.v7.widget.RecyclerView>
    </com.asha.ChromeLikeSwipeLayout>
    (2)Java:
    配置ChromeLikeSwipeLayout:
    ChromeLikeSwipeLayout chromeLikeSwipeLayout = (ChromeLikeSwipeLayout) findViewById(R.id.chrome_like_swipe_layout);
    ChromeLikeSwipeLayout.makeConfig()
            .addIcon(R.drawable.selector_icon_add)
            .addIcon(R.drawable.selector_icon_refresh)
            .addIcon(R.drawable.selector_icon_refresh)
            .addIcon(R.drawable.selector_icon_close)
            .radius(dp2px(35))
            .gap(dp2px(5))
            .circleColor(0xFF11CCFF)
            .listenItemSelected(new ChromeLikeSwipeLayout.IOnItemSelectedListener() {
                @Override
                public void onItemSelected(int index) {
                    Toast.makeText(RecyclerViewActivity.this, "onItemSelected:" + index, Toast.LENGTH_SHORT).show();
                }
            })
            .setTo(chromeLikeSwipeLayout);
    3.缺陷
    (1)无法实现list中一拉到底
    (2)安卓的list弹性效果使得下拉灵敏度下降

    4.实现原理
    (1)MotionEvent的分发和消费
    ChromeLikeSwipeLayout拦截逻辑:
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        //Log.d(TAG,"onInterceptTouchEvent:" + event);
        if (!mEnabled) return false;
        if ( mAnimationStarted ) return false;
        if ( canChildDragDown(mTouchManager.event2Point(event)) ) return false;
        return mTouchManager.onFeedInterceptEvent(event);
    }
    private boolean canChildDragDown(PointF pointF){
        ensureTarget();
        if ( mTarget instanceof TouchAlwaysTrueLayout )
            return ((TouchAlwaysTrueLayout) mTarget).canChildDragDown(pointF);
        else return ViewCompat.canScrollVertically(mTarget,-1);
    
    }
    mTarget即child view的RecyclerView,通过API ViewCompat. canScrollVertically ()判断当前RecyclerView是否还能够向上滑动,如果还能,则不拦截MotionEvent,交给child处理;如果不能,由自己处理。另一个成员变量mChromeLikeLayout是下拉出来的动效区。
    TouchManager.onFeedInterceptEvent():
    public boolean onFeedInterceptEvent(MotionEvent event){
        int action = event.getAction();
        switch ( action & MotionEvent.ACTION_MASK  ) {
            case MotionEvent.ACTION_DOWN:
                setActivePointerId(event, 0);
                if ( mBeginDragging ){
                    return true;
                }
                final float initialDownY = getCurrentMotionEventY(event);
                if (initialDownY == -1) return false;
                mTouchDownActor = initialDownY;
                mBeginDragging = false;
                if ( mTouchCallback != null ) mTouchCallback.onActionDown();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                resetActivePointerId();
                break;
            case MotionEvent.ACTION_MOVE:
                if ( mActivePointerId == TouchManager.INVALID_POINTER) {
                    //Log.e(TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                    return false;
                }
                final float y = getCurrentMotionEventY(event);
                if (y == -1) {
                    return false;
                }
                // if diff > mTouchSlop
                // let's drag!
                if ( mInterceptEnabled && !mBeginDragging && y - mTouchDownActor > mTouchSlop ) {
                    mBeginDragging = true;
                    if (mTouchCallback != null) mTouchCallback.onBeginDragging();
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(event);
                break;
        }
        return mBeginDragging;
    }
    可以看到,必不可少的初始化/启动下拉区域的逻辑是在Down事件中处理。如果未经Down事件,即使有Move事件进入处理,仍然会返回false,所以在ChromeLikeSwipeLayout.onInterceptTouchEvent()返回false,不拦截MotionEvent,下拉的动效也就不会被启动。这是3(1)的原因。

    ChromeLikeSwipeLayout消费逻辑:
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mTouchManager.onFeedTouchEvent(event);
    }
    TouchManager.onFeedTouchEvent():
    public boolean onFeedTouchEvent(MotionEvent event){
        final int action = MotionEventCompat.getActionMasked(event);
        int pointerIndex = MotionEventCompat.findPointerIndex(event, mActivePointerId);
        if (pointerIndex < 0) {
            //Log.e(TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
            return false;
        }
        final float y = MotionEventCompat.getY(event, pointerIndex);
        setTopOffset(y);
    
        boolean isExpanded = mTopOffset >= mThreshold && mBeginDragging;
        //first point
    
        switch ( action ) {
            case MotionEvent.ACTION_DOWN:
                setActivePointerId(event, 0);
                break;
            case MotionEvent.ACTION_CANCEL:
                if ( mTouchCallback != null ) mTouchCallback.onActionCancel(isExpanded);
                break;
            case MotionEvent.ACTION_UP:
                if ( mTouchCallback != null ) mTouchCallback.onActionUp(isExpanded);
                resetActivePointerId();
                break;
            case MotionEvent.ACTION_MOVE:
                mMotionX = (int) MotionEventCompat.getX(event,pointerIndex);
                if ( mTouchCallback != null ) mTouchCallback.onActionMove(isExpanded, this);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                pointerIndex = MotionEventCompat.getActionIndex(event);
                if (pointerIndex < 0) {
                    //Log.e(TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
                    return false;
                }
                setActivePointerId(event, pointerIndex);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(event);
                break;
        }
        return true;
    }

    这里的mTouchCallback实际上就是ChromeLikeSwipeLayout自身。在(2)中继续看细节。

    (2)位置计算/布局刷新/绘制水滴
    ChromeLikeSwipeLayout对于MotionEvent的处理最重要的是Move:
    @Override
    public void onActionMove(boolean isExpanded, TouchManager touchManager) {
        mChromeLikeLayout.onActionMove(isExpanded, touchManager);
        ensureTarget();
        View child = mTarget;
        int currentTop = child.getTop();
        if ( mTouchManager.isBeginDragging() ) {
            if ( !isExpanded )
                notifyOnExpandListeners(mTouchManager.calExpandProgress(currentTop), true);
            childOffsetTopAndBottom(mTouchManager.calTargetTopOffset(currentTop));
        }
    }
    

    三部分重要的逻辑:
    第一, ChromeLikeLayout.onActionMove()
    主要处理x向的move:
    根据当前移动到的位置,更新mPath,并调用invalidate()触发draw,绘制水滴拉伸效果。
    根据当前移动到的位置,决定是否更新当前的x向移动所位于的icon位置,以及是否启动水滴跳跃icon的动画。
    (水滴拉伸效果的绘制细节以及跳跃动画细节暂不展开。)
    第二,TouchManager.calExpandProgress()->notifyOnExpandListeners()
    计算当前有没有下拉到动效区域完整出现,并且将出现的比例值(float,>=0)通知给回调。可以根据这个值是否为0来知道当前动效区域有没有被打开以及有没有完整展示。
    第三,TouchManager.calTargetTopOffset()->childOffsetTopAndBottom()
    主要处理y向的move,以计算并layout动效区域位置。重点看看这部分逻辑:
    先看 childOffsetTopAndBottom():
    private void childOffsetTopAndBottom(int target){
        mTarget.offsetTopAndBottom( target );
        mChromeLikeLayout.offsetTopAndBottom( target );
        requestLayout();
    }

    看到是使用安卓api View.offsetTopAndBottom(int offset)来移动位置,并使用requestLayout()来触发layout。
    offset参数来自TouchManager.calTargetTopOffset():
    @Override
    public void onActionMove(boolean isExpanded, TouchManager touchManager) {
        mChromeLikeLayout.onActionMove(isExpanded, touchManager);
        ensureTarget();
        View child = mTarget;
        int currentTop = child.getTop();
        if ( mTouchManager.isBeginDragging() ) {
            if ( !isExpanded )
                notifyOnExpandListeners(mTouchManager.calExpandProgress(currentTop), true);
            childOffsetTopAndBottom(mTouchManager.calTargetTopOffset(currentTop));
        }
    }

    先从mTarget(即list child view)取到top值,传给 TouchManager .calTargetTopOffset()。TouchManager:
    public int calTargetTopOffset(int currentTop){
        return calTargetTopOffset(currentTop,getTopOffset());
    }
    
    public int calTargetTopOffset(int currentTop, int offset){
        int target;
        if ( currentTop <= sThreshold2 ) {
            if ( offset < 0 ){
                target = 0 - currentTop;
            } else if ( offset < sThreshold2 ) {
                target = offset - currentTop;
            } else {
                target = sThreshold2 - currentTop;
            }
        } else {
            target = sThreshold2 - currentTop;
        }
        return target;
    }
    

    看到计算这个offset包含个参数currentTop(前面介绍过),offset。第二个参数从getTopOffset()拿到:
    public int getTopOffset() {
        return mTopOffset;
    }

    实际上是取一个成员变量mTopOffset,那么这个成员变量的维护逻辑是什么?
    private void setTopOffset(float y) {
        mTopOffset = motionY2TopOffset(y);
    }

    TouchManager.onFeedTouchEvent()中被调用,且调用的时间早于ChromeLikeSwipeLayout.onActionMove():
    final float y = MotionEventCompat.getY(event, pointerIndex);
    setTopOffset(y);

    y是一个全局的绝对坐标概念,而前面的currentTop是一个相对概念,即child在parent中的相对位置坐标。
    motionY2TopOffset(y)起到了将y从绝对坐标转化成相对值的作用:
    private int motionY2TopOffset(float y){
        float original = y - mTouchDownActor;
        float basic = original * 0.6f;
        if ( basic > mThreshold){
            basic = mThreshold + (basic - mThreshold) * 0.3f;
        }
        return (int) basic;
    }

    其中,
    mTouchDownActor 是y的初始值,在处理Down事件的时候会赋值,相当于是记录初始位置;
    mThreshold是动效区域完整出现的高度,是个常量。可以看到一个明显的衰减逻辑,即在下拉累积的高度大于 mThreshold的时候,超出的部分会再*0.3。
    最关键的是第一句 float original=y- mTouchDownActor ;将绝对坐标转化成相对位移(下拉)。

    分析完了这两个重要的参数,再回头看 calTargetTopOffset()的逻辑就比较清楚了:
    public int calTargetTopOffset(int currentTop, int offset){
        int target;
        if ( currentTop <= sThreshold2 ) {
            if ( offset < 0 ){
                target = 0 - currentTop;
            } else if ( offset < sThreshold2 ) {
                target = offset - currentTop;
            } else {
                target = sThreshold2 - currentTop;
            }
        } else {
            target = sThreshold2 - currentTop;
        }
        return target;
    }

    sThreshold2 是一个常量,定义动效区域最大的高度;
    offset如果大于 sThreshold2 ,则always返回 sThreshold2 -currentTop,即最大只能下拉到 sThreshold2 的高度;
    否则,offset如果位于[0, sThreshold2 ],返回offset-currentTop,即将child下移到offset的高度;
    否则,offset如果<0,返回 0 -currentTop,即child上移返回到动效区处于隐藏的初始状态。从motionY2TopOffset( float y)可以看到,只有y< mTouchDownActor 的时候,才会返回一个小于零的值,也就是手势滑到初始位置之上了,关闭动效区域。

    最后看看如何在仅仅调用了两个child view的offsetTopAndBottom()情况下,保证动效区域的视图垂直位置上居中。通过两块逻辑实现:

    ChromeLikeLayout.onLayout():
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int startXOffset = getCircleStartX();
        int startYOffset = (b - t);
    
        for (int i = 0 ; i < getChildCount() ; i++ ){
            View view = getChildAt(i);
            final int left = startXOffset + i * getItemWidth() - view.getMeasuredWidth()/2;
            final int right = left + view.getMeasuredWidth();
            final int top = (startYOffset - view.getMeasuredHeight())>>1;
            final int bottom = top + view.getMeasuredHeight();
            view.layout(left,top,right,bottom);
        }
    }

    动效区域的ViewGroup.onLayout(),看到top是(b-t-view.getMeasuredHeight())/2,保证child(动效区域的icons)垂直居于动效区域的中间。

    ChromeLikeSwipeLayout.onLayout()/onMeasure():
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int width = getMeasuredWidth();
        final int height = getMeasuredHeight();
        if (getChildCount() == 0) {
            return;
        }
        if (mTarget == null) {
            ensureTarget();
        }
        if (mTarget == null) {
            return;
        }
        View child = mTarget;
        int childLeft = getPaddingLeft();
        int childTop = child.getTop();
        int childWidth = width - getPaddingLeft() - getPaddingRight();
        int childHeight = height - getPaddingBottom();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    
        child = mChromeLikeLayout;
        childLeft = getPaddingLeft();
        childTop = mTarget.getTop() - child.getMeasuredHeight();
        childWidth = width - getPaddingLeft() - getPaddingRight();
        childHeight = child.getMeasuredHeight();
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mTarget == null) {
            ensureTarget();
        }
        if (mTarget == null) {
            return;
        }
        final int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
        final int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
        mTarget.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        mChromeLikeLayout.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mTarget.getTop(), MeasureSpec.EXACTLY));
    }

    看到在onMeasure中强制给 mChromeLikeLayout 的width measure成 mTarget .getTop(),此后在onLayout的时候,对于 mChromeLikeLayout ,childTop= mTarget .getTop()-child.getMeasuredHeight();即childTop为0。也就是说,动效区域top始终为0。

    上述两部分逻辑保证 保证动效区域的视图垂直位置上居中。

    展开全文
  • 初识RecyclerView

    千次阅读 2016-01-30 18:31:44
    初识RecyclerView效果图RecyclerView与ListView对比(官方) RecyclerView 小组件比 ListView 更高级且更具灵活性。 此小组件是一个用于显示庞大数据集的容器,可通过保持有限数量的视图进行非常有效的...RecyclerVi
  • RecyclerView是Android 5.0的新特性,可以直接代替ListView与GridView,并且能够实现瀑布流的布局,感觉RecyclerView使用的好处就是它不关心布局,只关心资源的回收与复用,正因为如此,RecyclerView中将...RecyclerVi
  • RecyclerView嵌套问题

    千次阅读 2017-10-13 16:32:34
    当我们布局遇到一些复杂的布局时,或是一些特殊要求的布局时,会用到RecyclerView嵌套RecyclerView或者ScrollView嵌套RecyclerView的...1.RecyclerView嵌套RecyclerView我们经常在项目的类似朋友圈场景中使用RecyclerVi
  • 回顾 在上一篇博客中,我给大家介绍了怎么给 RecyclerView构造一个通用的适配器。...那们接下来,我们来试试怎么给 RecyclerView加上 HeaderView. 大家都知道,在 ListView中,我们只要通过 ...但是,换成 RecyclerVi
  • 在平时开发应用的时候,经常会遇到列表排序、滑动删除的需求。如果列表效果采用的是 ListView 的话,需要经过自定义 View 才能实现效果;...RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerVi
  • 核心代码:rv.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerVi...
  • 类京东商详页——一步实现TabLayout与...非常适合电商App商品详情页的锚点定位使用,并且支持CoordinatorLayout+AppBarLayout+TabLayout+RecyclerView(TabLayout与RecyclerView不重叠的情况)和TabLayout+RecyclerVi
  • 前言 RecyclerView在 Android开发中非常常用,如果能结合ItemDecoration类使用,那么将大大提高RecyclerView的表现效果 本文全面解析了ItemDecoration类,包括ItemDecoration类简介... ItemDecoration类属于RecyclerVi
  • Android代码-changeloglib

    2019-08-06 20:14:48
    ChangeLog Library ChangeLog Library provides an easy way to display a change log in your Android app. Travis master: Travis dev: Examples ...ChangeLog Library provides a custom RecyclerVi
  • RecyclerView 滑动时不让 Glide 加载图片

    千次阅读 2018-09-03 17:48:46
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerVi...
  • Android recycleview 二级列表(3) 调用 List<HealthArticleRecyclerViewAdapter.DataTree<HealthArticle,HealthArticle>> healthArticleList = healthyArticleAPresenter....recyclerVi...
  • RecyclerViewLibrary A RecyclerView libirary ,has some support, like headerAdapter/TreeAdapter,and PulltoRefreen/Drag 一个RecyclerView扩展库,当中主要包括 ...1:针对RecyclerView数据...2:RecyclerVi...
  • 在app/build.gradle文件中dependencies里面添加 def recyclerview_version ="1.1.0" implementation "androidx.recyclerview:recyclerview:$recyclerview_version" ...implementation "androidx.recyclervi
  • UIBestPractice

    2017-04-23 21:06:25
    MainActivity.Class public class MainActivity extends AppCompatActivity { private List msgList = new ArrayList(); private EditText inputText; private Button send;... private RecyclerVi
  • Recyclerview的 多选 单选 全选 去不选 mainactivity public class MainActivity extends AppCompatActivity { private boolean isType = false;... private List list = new ArrayList();... private Recyclervi
  • 先看看效果图: 知识点分析 效果图来看不复杂内容并没多少,值得介绍一下的知识点也就下面几个吧 – 列表标题悬停 – 左右列表滑动时联动 – 添加商品时的抛物线动画 ...1)、sticky-headers-recyclervi
  • 第七篇为滑动和拖拽 package ... import android.annotation.SuppressLint; import android.graphics.Canvas; import android.view.MotionEvent;...import android.view.View;...import androidx.recyclervi
  • 技能2

    2019-08-23 21:43:01
    下面的运用了PullToRefreshRecyclerView 代码 package ... import android.os.Bundle; import androidx.annotation.NonNull;...import androidx.fragment.app.Fragment;...import androidx.recyclervi...
  • 一、RecycleViewActivity  /** * RecycleView+recycleView.Adapter+RecycleView.ViewHolder */ public class RecycleViewActivity extends AppCompatActivity { private List models;... private RecyclerVi
  • github: 点击打开链接 效果: 用法: 1 RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);...recyclerVi
  • android模拟聊天界面recyclerview实现

    万次阅读 2017-03-03 18:53:43
    package com.itheima74.chatui...import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerVi
  • weekone

    2019-02-17 20:26:22
    依赖 //gson implementation 'com.google.code.gson:gson:2.8.5' //okhttp implementation 'com.squareup.okhttp3:okhttp:3.7.0' //recyclerview ... implementation 'com.android.support:recyclervi...
  • Viewpager2

    2021-04-28 16:07:04
    首先,Viewpager2需要项目是已适配AndroidX了,因为, 如需使用 ViewPager2,需要先将以下 AndroidX 依赖项添加到项目的 build.gradle 文件: dependencies { implementation "androidx.viewpager2:...RecyclerVi
  • RecyclerView实现多种item布局 动态改变每一行的列数(动态控制布局管理器): 重写adpater中的onAttachedToRecyclerView()方法: 动态改变布局管理器调整item行数... RecyclerView.LayoutManager manager = recyclerVi
  • 今天我们不学习这些控件的具体的使用,今天我们来看一个在实际工作中会遇到的AppBarLayout+CoordinatorLayout+RecyclerView(其中包含横向滑动的RecyclerVi...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 414
精华内容 165
关键字:

RecyclerVi