Android高仿「陌陌」的點(diǎn)點(diǎn)效果

寫在前面的話

寫代碼一直不太喜歡加注解彼哼,但是代碼的思路很清晰巍实,接下來會對具體實現(xiàn)簡單講解

廢話不說,先上效果圖:
運(yùn)行效果圖
分析

從效果上看圖片的展示具有層次感姜贡,在數(shù)據(jù)結(jié)構(gòu)上更像是stack,所以通過繼承FrameLayout來實現(xiàn)(不清楚FrameLayout布局特點(diǎn)的可以先百度下哈)撩穿,外面是通過繼承FrameLayout自定義的TinderStackLayout磷支,它主要作為一個容器,里面的卡片效果也是繼承FrameLayout自定義TinderCardView食寡,需要對其onTouch()事件進(jìn)行監(jiān)聽雾狈,設(shè)置閥值來判斷TinderCardView的移動距離,然后通過設(shè)置動畫即可實現(xiàn)抵皱。

具體實現(xiàn)

TinderCardView實現(xiàn)
1:在布局上最外層使用CardView,里面包含兩部分內(nèi)容善榛,上面ImageView,下面TextView.通過inflate設(shè)置內(nèi)容視圖。在最頂層還需要一個ImageView來作為Tips
2:通過變量downX叨叙,downY來記錄按下時的坐標(biāo)位置锭弊。
3:在移動過程中dX,dY來記錄視圖移動的坐標(biāo)堪澎,然后重新設(shè)置移動后的視圖位置擂错,同時還要計算和設(shè)置視圖的旋轉(zhuǎn)角度以及tips的透明度等。
4:當(dāng)手指抬起的時候需要判斷視圖向左或者向右移動的距離是否超過設(shè)定的閥值樱蛤,如果超過了就從父容器TinderCardLayout中移除钮呀,否則重置視圖位置剑鞍。
5:在移除視圖之后需要判斷父容器內(nèi)子視圖的數(shù)量,如果僅剩下一個子視圖爽醋,就通知TinderCardLayout加載更多蚁署。

public class TinderCardView extends FrameLayout implements View.OnTouchListener {
    private static final int PADDINGVALUE=16;
    private static final float CARD_ROTATION_DEGREES = 40.0f;
    public static final int DURATIONTIME=300;
    private ImageView iv;
    private TextView tv_name;
    private ImageView iv_tips;
    private int padding;
    private float downX;
    private float downY;
    private float newX;
    private float newY;
    private float dX;
    private float dY;
    private float rightBoundary;
    private float leftBoundary;
    private int screenWidth;
    private OnLoadMoreListener listener;

    public TinderCardView(Context context) {
        this(context,null);
    }

    public TinderCardView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public TinderCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public void init(Context context){

        if(!isInEditMode()){
            inflate(context,R.layout.cardview,this);
            screenWidth=DensityUtil.getScreenWidth(context);
            leftBoundary =  screenWidth * (1.0f/6.0f);
            rightBoundary = screenWidth * (5.0f/6.0f);
            iv=(ImageView) findViewById(R.id.iv);
            tv_name=(TextView) findViewById(R.id.tv_name);
            iv_tips=(ImageView)findViewById(R.id.iv_tips);
            padding = DensityUtil.dip2px(context, PADDINGVALUE);
            setOnTouchListener(this);

        }

    }

    @Override
    public boolean onTouch(final View view, MotionEvent motionEvent) {
        TinderStackLayout tinderStackLayout = ((TinderStackLayout) view.getParent());
        TinderCardView topCard = (TinderCardView) tinderStackLayout.getChildAt(tinderStackLayout.getChildCount() - 1);
        if (topCard.equals(view)) {
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = motionEvent.getX();
                    downY = motionEvent.getY();
                    view.clearAnimation();
                    return true;
                case MotionEvent.ACTION_MOVE:
                    newX = motionEvent.getX();
                    newY = motionEvent.getY();
                    dX = newX - downX;
                    dY = newY - downY;
                    float posX = view.getX() + dX;
                    view.setX(view.getX() + dX);
                    view.setY(view.getY() + dY);
                    float rotation = (CARD_ROTATION_DEGREES * (posX)) / screenWidth;
                    int halfCardHeight = (view.getHeight() / 2);
                    if(downY < halfCardHeight - (2*padding)){
                        view.setRotation(rotation);
                    } else {
                        view.setRotation(-rotation);
                    }
                    float alpha = (posX - padding) / (screenWidth * 0.3f);
                    if(alpha>0){
                        iv_tips.setAlpha(alpha);
                        iv_tips.setImageResource(R.drawable.ic_like);
                    }else{
                        iv_tips.setAlpha(-alpha);
                        iv_tips.setImageResource(R.drawable.ic_nope);

                    }

                    return true;
                case MotionEvent.ACTION_UP:
                    if(isBeyondLeftBoundary(view)){
                        removeCard(view, -(screenWidth * 2));
                    }
                    else if(isBeyondRightBoundary(view)){
                        removeCard(view,(screenWidth * 2));

                    }else{
                        resetCard(view);
                    }


                    return true;
                default :
                    return super.onTouchEvent(motionEvent);
            }
        }
        return super.onTouchEvent(motionEvent);

    }

    private boolean isBeyondLeftBoundary(View view){
        return (view.getX() + (view.getWidth() / 2) < leftBoundary);
    }

    private boolean isBeyondRightBoundary(View view){
        return (view.getX() + (view.getWidth() / 2) > rightBoundary);
    }

    private void removeCard(final View view, int xPos){
        view.animate()
                .x(xPos)
                .y(0)
                .setInterpolator(new AccelerateInterpolator())
                .setDuration(DURATIONTIME)
                .setListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animator) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                       ViewGroup viewGroup = (ViewGroup) view.getParent();
                        if(viewGroup != null) {
                            viewGroup.removeView(view);
                        }
                        int count=viewGroup.getChildCount();
                        if(count==1 && listener!=null){
                            listener.onLoad();
                        }
                    }
                    @Override
                    public void onAnimationCancel(Animator animator) {

                    }

                    @Override
                    public void onAnimationRepeat(Animator animator) {

                    }
                });
    }


    private void resetCard(final View view){

        view.animate()
                .x(0)
                .y(0)
                .rotation(0)
                .setInterpolator(new OvershootInterpolator())
                .setDuration(DURATIONTIME);
        iv_tips.setAlpha(0f);

    }

    public void bind(User u){
        if(u==null){
            return;
        }
        if(!TextUtils.isEmpty(u.getAvatarUrl())){
            Glide.with(iv.getContext())
                    .load(u.getAvatarUrl())
                    .into(iv);
        }
        if(!TextUtils.isEmpty(u.getName())){
            tv_name.setText(u.getName());
        }
    }

    public interface OnLoadMoreListener{
        void onLoad();
    }



    public void setOnLoadMoreListener(OnLoadMoreListener listener){
        this.listener=listener;
    }
}

TinderStackLayout實現(xiàn)
作為父容器,通過addView()方法添加子視圖蚂四,設(shè)置變量STACK_SIZE來表示父容器內(nèi)可以展示的子視圖數(shù)量光戈。
在添加子視圖時需要計算和設(shè)置子視圖的縮放以及顯示位置(這樣才能達(dá)到層級顯示效果)
onLoad()方法作為數(shù)據(jù)的回調(diào),當(dāng)父容器里面只有一個子視圖時遂赠,就會調(diào)用該方法去加載數(shù)據(jù)久妆。

public class TinderStackLayout extends FrameLayout implements TinderCardView.OnLoadMoreListener {
    private ViewGroup.LayoutParams params = null;
    public static final float BASESCALE_X_VALUE = 50.0f;
    public static final int BASESCALE_Y_VALUE = 8;
    public static final int DURATIONTIME = 300;
    private static final int STACK_SIZE = 4;
    private int index = 0;
    private int scaleY;
    private TinderCardView tc;
    private List<User> mList;

    public TinderStackLayout(Context context) {
        this(context, null);
    }

    public TinderStackLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    public void init() {
        params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        scaleY = DensityUtil.dip2px(getContext(), BASESCALE_Y_VALUE);
    }

    private void addCard(TinderCardView view) {
        int count = getChildCount();
        addView(view, 0, params);
        float scaleX = 1 - (count / BASESCALE_X_VALUE);
        view.animate()
                .x(0)
                .y(count * scaleY)
                .scaleX(scaleX)
                .setInterpolator(new AnticipateOvershootInterpolator())
                .setDuration(DURATIONTIME);
    }

    public void setDatas(List<User> list) {
        this.mList = list;
        if (mList == null) {
            return;
        }
        for (int i = index; index < i + STACK_SIZE; index++) {
            tc = new TinderCardView(getContext());
            tc.bind(mList.get(index));
            tc.setOnLoadMoreListener(this);
            addCard(tc);
        }
    }

    @Override
    public void onLoad() {
        for (int i = index; index < i + (STACK_SIZE - 1); index++) {
            if (index == mList.size()) {
                return;
            }
            tc = new TinderCardView(getContext());
            tc.bind(mList.get(index));
            tc.setOnLoadMoreListener(this);
            addCard(tc);
        }
        int childCount = getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            TinderCardView tinderCardView = (TinderCardView) getChildAt(i);
            if (tinderCardView != null) {
                float scaleValue = 1 - ((childCount - 1 - i) / 50.0f);
                tinderCardView.animate()
                        .x(0)
                        .y((childCount - 1 - i) * scaleY)
                        .scaleX(scaleValue)
                        .rotation(0)
                        .setInterpolator(new AnticipateOvershootInterpolator())
                        .setDuration(DURATIONTIME);
            }
        }
    }
}

DensityUtil工具類
主要是獲取設(shè)備的屏幕寬度以及dip與px之間的轉(zhuǎn)化(計算偏移量和縮放比例需要)。

public class DensityUtil {
    
    public static int getScreenWidth(Context context){
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        return wm.getDefaultDisplay().getWidth();
    }

    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
}

User實體類
具體的數(shù)據(jù)模型跷睦,封裝需要展示的基本信息筷弦。

private String name;
    private String avatarUrl;

    public User(String name, String avatarUrl) {
        this.name = name;
        this.avatarUrl = avatarUrl;
    }
    public User() {

    }
    public String getAvatarUrl() {
        return avatarUrl;
    }

    public void setAvatarUrl(String avatarUrl) {
        this.avatarUrl = avatarUrl;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

cardview.xml

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="36dp"
    app:cardCornerRadius="5dp"
    android:layout_gravity="top">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:id="@+id/iv"
                android:layout_width="match_parent"
                android:layout_height="320dp"
                android:layout_marginBottom="5dp"
                android:scaleType="centerCrop"
                android:src="@mipmap/ic_launcher"/>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:gravity="center_vertical"
                android:padding="16dp"
                android:background="@android:color/white"
                android:layout_gravity="bottom">

                <TextView
                    android:id="@+id/tv_name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif"
                    android:text="Mersens"
                    android:textSize="22sp"/>
            </LinearLayout>

        </FrameLayout>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="top"
            android:clipChildren="true">
            <ImageView
                android:id="@+id/iv_tips"
                android:layout_height="62dp"
                android:layout_width="62dp"
                android:src="@drawable/ic_like"
                android:layout_gravity="center"
                android:layout_marginBottom="50dp"
                android:alpha="0"
                />
        </FrameLayout>
    </FrameLayout>

    </android.support.v7.widget.CardView>

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.mersens.tinderstackview.activity.MainActivity">
    <com.mersens.tinderstackview.view.TinderStackLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/tinderStackLayout"
        />
</LinearLayout>

MainActivity
在MainActivity中的數(shù)據(jù)來源來自于values目錄下的arrays.xml(這里就不再具體描述,詳見源碼)抑诸,然后把數(shù)據(jù)傳遞給TinderStackLayout進(jìn)行管理

public class MainActivity extends AppCompatActivity {
    private TinderStackLayout tinderStackLayout;
    private List<User> mList;
    private String names[];
    private String avatarUrls[];

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

    public void init(){
        mList=new ArrayList<User>();
        names=getResources().getStringArray(R.array.names);
        avatarUrls=getResources().getStringArray(R.array.avatar_urls);
        tinderStackLayout=(TinderStackLayout)findViewById(R.id.tinderStackLayout);
        for(int i=0;i<names.length;i++){
            User user=new User(names[i],avatarUrls[i]);
            mList.add(user);
        }
        tinderStackLayout.setDatas(mList);

    }
}
這里奉上源代碼以及參考資料
GitHub:https://github.com/Mersens/TinderStackView
參考資料:https://github.com/lawloretienne/TinderStack
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烂琴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜕乡,更是在濱河造成了極大的恐慌奸绷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件异希,死亡現(xiàn)場離奇詭異健盒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)称簿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門扣癣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憨降,你說我怎么就攤上這事父虑。” “怎么了授药?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵士嚎,是天一觀的道長。 經(jīng)常有香客問我悔叽,道長莱衩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任娇澎,我火速辦了婚禮笨蚁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己括细,他們只是感情好伪很,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奋单,像睡著了一般锉试。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上览濒,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天呆盖,我揣著相機(jī)與錄音,去河邊找鬼贷笛。 笑死絮短,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昨忆。 我是一名探鬼主播丁频,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼邑贴!你這毒婦竟也來了席里?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤拢驾,失蹤者是張志新(化名)和其女友劉穎奖磁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體繁疤,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咖为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了稠腊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躁染。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖架忌,靈堂內(nèi)的尸體忽然破棺而出吞彤,到底是詐尸還是另有隱情,我是刑警寧澤叹放,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布饰恕,位于F島的核電站,受9級特大地震影響井仰,放射性物質(zhì)發(fā)生泄漏埋嵌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一俱恶、第九天 我趴在偏房一處隱蔽的房頂上張望雹嗦。 院中可真熱鬧拌喉,春花似錦、人聲如沸俐银。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捶惜。三九已至,卻和暖如春荔烧,著一層夾襖步出監(jiān)牢的瞬間吱七,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工鹤竭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踊餐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓臀稚,卻偏偏與公主長得像吝岭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吧寺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容