1. 程式人生 > >仿約會吧應用詳情頁,評論欄隨Recyclerview滑動左右移動

仿約會吧應用詳情頁,評論欄隨Recyclerview滑動左右移動

背景

最近在玩一個叫“約會吧”的應用,也是在看直播app,預設下載安裝的,安裝點進去看這個應用做的不錯,就留下來了。然後看他們動態詳情頁底部有一個效果:Recyclerview滑動到的評論列表的時候,底部點贊那欄會往左滑動,出現一個輸入評論的欄;然後下拉到底部的時候輸入評論欄會往右滑動,出現點贊欄。詳細細節直接來看效果圖吧。

這裡寫圖片描述

其實這種效果現在在應用中還是很常見的,有上拉,toolbar、底部view隱藏,下拉顯示,或者像現在約會吧這樣左右滑動的效果。而且網上資料現在也有很多,比如RecyclerView上拉隱藏Toolbar,下拉顯示效果 這篇文章通過ObjectAnimation來實現。想用這種方法的直接去看這篇文章。這裡我們通過另外一種方法來實現。仔細下看下這個效果,其實他就是view滾動的效果,想到android裡面的滾動,馬上就能想到scroller類了,scroller有一個startScroll()方法,通過這個方法我們就可以滾動了。滾動問題解決了,那麼這個效果就很簡單了,進入頁面時,把要顯示view的先顯示出來,不該顯示的暫時放在螢幕外面,當滾動的時間,我們控制view進入螢幕或者退出螢幕。大概思路就是這樣,下面我們就來實現這樣的效果吧。

效果的實現

首先,我們根據上面的思路把佈局給整出來。結構如下圖:

這裡寫圖片描述

這裡說明下上面的圖,分為3塊來說,
- 當Recyclerview上拉的時候,螢幕內5位置的view會隱藏,也就是移動到螢幕外面的6位置,當Recyclerview下拉的時候,螢幕外面的6位置view又會回到5位置顯示。
- 當Recyclerview上拉的時候,螢幕內的1位置的view會隱藏,也就是移動到螢幕外面的4位置,當Recyclerview下拉的時候,螢幕外面的4位置view會回到1位置顯示。
- 當RecyclerView上拉的時候,而且設定為水平方向左右滑動的時候,螢幕內的1位置的view會移動到3位置,同時螢幕外面2位置view會移動到螢幕內1位置來顯示,當RecyclerView下拉的時候,螢幕外的3位置會移動到螢幕內的1位置。1位置顯示的view也會回到螢幕外的2位置隱藏。這也就是上面應用的效果。

佈局效果和程式碼如下(這裡新增兩個按鈕來切換底部方向的效果):

            效果圖                                         

activity_main.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="match_parent"
android:background="@android:color/white">
<android.support.v7.widget.RecyclerView android:id="@+id/id_recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" /> <RelativeLayout android:id="@+id/id_horization_rl" android:layout_width="match_parent" android:layout_height="60dp" android:layout_alignParentBottom="true" android:orientation="horizontal" > <TextView android:id="@+id/id_bottom_float" android:layout_width="match_parent" android:layout_height="60dp" android:text="我是點贊操作佈局" android:textSize="18sp" android:gravity="center" android:background="#E2E2E2"> </TextView> <TextView android:id="@+id/id_bottom_comment" android:layout_width="match_parent" android:layout_height="60dp" android:text="我是評論輸入佈局" android:textSize="18sp" android:gravity="center" android:background="#FF4500"> </TextView> </RelativeLayout> <TextView android:id="@+id/id_bottom_vertical" android:layout_width="match_parent" android:layout_height="60dp" android:text="你滑動,我隨你而變" android:layout_alignParentBottom="true" android:background="#eeeeee" android:gravity="center" android:textSize="16sp" /> <TextView android:id="@+id/id_top_vertical" android:layout_width="match_parent" android:layout_height="60dp" android:text="你滑動,我隨你而變" android:background="#eeeeee" android:gravity="center" android:textSize="16sp" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/id_switch" android:orientation="vertical" android:layout_alignParentRight="true" android:layout_centerVertical="true"> <TextView android:layout_width="wrap_content" android:layout_height="60dp" android:gravity="center" android:background="#eeeeee" android:text="切換底部水平動畫" android:onClick="showHorization"/> <TextView android:layout_width="wrap_content" android:layout_height="60dp" android:gravity="center" android:background="#eeeeee" android:onClick="showVertical" android:layout_marginTop="10dp" android:text="切換底部垂直動畫"/> </LinearLayout> </RelativeLayout>

然後,我們再寫一個執行緒來實現滾動的效果。程式碼如下:

public class AnimationUtil implements Runnable{
    private Context mContext;
    //傳入需要操作的view
    private View mAnimationView;
    //view的寬和高
    private int mViewWidth;
    private int mViewHeight;
    //動畫執行時間
    private final int DURATION = 400;
    //是水平還是垂直滑動變化
    public boolean mOrientaion ;
    //滾動操作類
    private Scroller mScroller;
    private boolean isShow;
    public AnimationUtil(Context context,final View mAnimationView){
        this.mContext = context ;
        this.mAnimationView = mAnimationView ;
        mScroller = new Scroller(context,new LinearInterpolator());
        //水平佈局這裡以螢幕寬為準
        mViewWidth = getScreenWidth();
        mViewHeight = mAnimationView.getMeasuredHeight();
        if(mViewHeight==0){
            mAnimationView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    mAnimationView.getViewTreeObserver().removeOnPreDrawListener(this);
                    mViewHeight = mAnimationView.getMeasuredHeight();
                    return true;
                }
            });
        }
    }

    public void setOrientaion(boolean isHorization){
        this.mOrientaion = isHorization;
    }
    //根據滑動變化,isScrollUp為true水平左邊滑動,否則反之,
    //為false垂直往下隱藏,否則反之,
    public void startHideAnimation(boolean isScrollUp){
        isShow = false ;
        if(!mOrientaion){
            int dy = (int) (mAnimationView.getTranslationY()+mViewHeight);
            if(!isScrollUp){
                dy = (int)(mAnimationView.getTranslationY() - mViewHeight);
            }
            dy = cling(-mViewHeight,mViewHeight,dy);
            mScroller.startScroll(0, (int) mAnimationView.getTranslationY(),0,dy,DURATION);
            ViewCompat.postOnAnimation(mAnimationView,this);
            return;
        }
        int dx = (int) (mAnimationView.getTranslationX()-mViewWidth);
        if(!isScrollUp){
            dx = (int)(mAnimationView.getTranslationX() + mViewWidth);
        }
        dx = cling(-mViewWidth,mViewWidth,dx);
        mScroller.startScroll((int)mAnimationView.getTranslationX(),0,dx,0,DURATION);
        ViewCompat.postOnAnimation(mAnimationView,this);
    }
    //顯示控制元件
    public void startShowAnimation(){
        isShow = true ;
        if(!mOrientaion){
            int dy = (int) ViewCompat.getTranslationY(mAnimationView);
            dy = cling(-mViewHeight,mViewHeight,dy);
            mScroller.startScroll(0,dy,0,-dy,DURATION);
            ViewCompat.postOnAnimation(mAnimationView,this);
            return;
        }
        int dx = (int) ViewCompat.getTranslationX(mAnimationView);
        dx = cling(-mViewWidth,mViewWidth,dx);
        mScroller.startScroll(dx,0,-dx,0,DURATION);
        ViewCompat.postOnAnimation(mAnimationView,this);
    }
    //判斷當前繫結動畫控制元件是否顯示,
    public boolean isShow() {
        return isShow;
    }

    //終止動畫
    public void abortAnimation(){
        mScroller.abortAnimation();
    }
    @Override
    public void run() {
        if(mScroller.computeScrollOffset()){
            //動畫沒停止就繼續滑動
            ViewCompat.postOnAnimation(mAnimationView,this);
            if(!mOrientaion){
                ViewCompat.setTranslationY(mAnimationView,mScroller.getCurrY());
                return;
            }
            ViewCompat.setTranslationX(mAnimationView,mScroller.getCurrX());
        }
    }
    public int getScreenWidth(){
        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(dm);
        return dm.widthPixels;
    }
    //控制在一個範圍的值
    public int cling(int min,int max,int value){
        return Math.min(Math.max(min, value), max);
    }
}

這裡簡單說下上面AnimationUtil執行緒,首先它會建立一個滾動操作類Scroller,然後獲取需要滾動的view的寬和高的獲取,這裡寬直接取螢幕的寬度。同時還有一個mOrientaion屬性,方向的控制。然後startHideAnimation和startShowAnimation兩個方法。其中startHideAnimation中,我們計算出每個效果的初始位置的x和y。然後x和y軸移動的偏移量,然後startScroll方法的呼叫,然後把通過ViewCompat.postOnAnimation把移動動畫繫結在傳入的view裡面。startShowAnimation方法也是同理。我們知道,呼叫了startScroll,只是告訴Scroller移動到什麼位置,具體的移動資訊是在computeScrollOffset獲取。所以我們通過這個方法就去判斷view是否移動完成,沒有移動,繼續呼叫當前執行緒,同時根據方向設定setTranslationY或者setTranslationX。

view滾動的幫助類實現完了,我們就寫個Recyclerview來簡單的測試下,MainActivity程式碼如下:

public class MainActivity extends AppCompatActivity {
    //通過recyclerview來提供滑動事件
    private RecyclerView mRecyclerView;
    //一些簡單的測試資料
    private TestAdapter mRecyclerAdapter;
    //水平簡單贊佈局view繫結動畫
    private AnimationUtil mZanAnimationUtil;
    //水平簡單評論佈局view繫結動畫
    private AnimationUtil mCommAnimationUtil;
    //垂直底部view繫結動畫
    private AnimationUtil mBottomVerticalUtil;
    //垂直頭頂view繫結佈局
    private AnimationUtil mTopVerticalUtil;
    private List<String> mDataList=Arrays.asList("對Ta說了悄悄話","衝哥","小歡","物件,你在哪","暖心男神","一次就好",
            "對Ta說了悄悄話","衝哥","小歡","物件,你在哪","暖心男神","一次就好",
            "對Ta說了悄悄話","衝哥","小歡","物件,你在哪","暖心男神","一次就好",
            "對Ta說了悄悄話","衝哥","小歡","物件,你在哪","暖心男神","一次就好");
    private LinearLayoutManager mRecyclerManager;
    //贊佈局控制元件
    private TextView mZanTextView;
    //評論佈局控制元件
    private TextView mCommentView;
    private RelativeLayout mHorizationalRl;
    //底部佈局控制元件
    private TextView mVerticalBottomTv;
    //頭部佈局控制元件
    private TextView mVerticalTopTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerview);
        mZanTextView = (TextView) findViewById(R.id.id_bottom_float);
        mCommentView = (TextView)findViewById(R.id.id_bottom_comment) ;
        mVerticalBottomTv = (TextView)findViewById(R.id.id_bottom_vertical);
        mHorizationalRl = (RelativeLayout)findViewById(R.id.id_horization_rl) ;
        mVerticalTopTv = (TextView)findViewById(R.id.id_top_vertical);
        mZanAnimationUtil = new AnimationUtil(this,mZanTextView);
        mCommAnimationUtil = new AnimationUtil(this,mCommentView);
        mBottomVerticalUtil = new AnimationUtil(this,mVerticalBottomTv);
        mTopVerticalUtil = new AnimationUtil(this,mVerticalTopTv);
        mZanAnimationUtil.setOrientaion(true);
        mCommAnimationUtil.setOrientaion(true);
        mCommAnimationUtil.startHideAnimation(false);
        mHorizationalRl.setVisibility(View.GONE);
        mRecyclerManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mRecyclerManager);
        mRecyclerAdapter = new TestAdapter(mDataList,this);
        mRecyclerView.setAdapter(mRecyclerAdapter);
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                //當滑動停止時動畫開始
                if(newState == RecyclerView.SCROLL_STATE_IDLE){
                    //在到達某個item改變水平佈局
                    if(mRecyclerManager.findFirstVisibleItemPosition()>4){
                        mZanAnimationUtil.startHideAnimation(true);
                        mCommAnimationUtil.startShowAnimation();
                    }else{
                        mZanAnimationUtil.startShowAnimation();
                        if(mCommAnimationUtil.isShow()){
                            mCommAnimationUtil.startHideAnimation(false);
                        }
                    }
                    //頭部和底部動畫操作
                    if(mRecyclerManager.findFirstVisibleItemPosition()>0){
                        mBottomVerticalUtil.startHideAnimation(true);
                        mTopVerticalUtil.startHideAnimation(false);
                    }else{
                        mBottomVerticalUtil.startShowAnimation();
                        mTopVerticalUtil.startShowAnimation();
                    }
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

            }
        });
    }
    public void showVertical(View view){
        mHorizationalRl.setVisibility(View.GONE);
        mVerticalBottomTv.setVisibility(View.VISIBLE);
    }
    public void showHorization(View view){
        mHorizationalRl.setVisibility(View.VISIBLE);
        mVerticalBottomTv.setVisibility(View.GONE);
    }
}

主要是onScrollStateChanged方法裡面的操作。主要就是注意下評論佈局控制元件的初始化就好了。

再貼下其他的類
TestAdapter.class

public class TestAdapter extends RecyclerView.Adapter<TestAdapter.SimpleViewHolder>{
    private List<String> mDataList;
    private Context mContext;
    private LayoutInflater mInflater;

    public TestAdapter(List<String> mDataList, Context mContext) {
        this.mDataList = mDataList;
        this.mContext = mContext;
        mInflater = LayoutInflater.from(mContext);
    }

    @Override
    public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new SimpleViewHolder(mInflater.inflate(R.layout.simple_item,parent,false));
    }

    @Override
    public void onBindViewHolder(SimpleViewHolder holder, int position) {
        holder.mTextView.setText(mDataList.get(position));
    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    public class SimpleViewHolder extends RecyclerView.ViewHolder{
        private TextView mTextView;
        public SimpleViewHolder(View itemView) {
            super(itemView);
            this.mTextView = (TextView)itemView.findViewById(R.id.id_text);
        }
    }
}

simple_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:orientation="horizontal"
        android:gravity="center_vertical">
        <ImageView
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="#EEEEEE"
            android:layout_margin="10dp"
            android:src="@drawable/post_default_avatar"/>
        <TextView
            android:id="@+id/id_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="21111111"
            android:textSize="14sp"
            android:layout_marginLeft="10dp"/>
    </LinearLayout>
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:layout_marginTop="10dp"
        android:background="#eeeeee"/>
</LinearLayout>

最後,看下實現的效果:

這裡寫圖片描述

這裡 開發環境為android studio 2.1.0 -preview4

程式碼下載