Android 堆疊式布局實現(xiàn)(一)簡單堆疊式布局

寫在前面的幾句話

<p>
堆疊式布局,顧明思議,堆在一起的布局,我百度了一下好像還真沒有太多關于這個方面的講解,剛好最近學習的時候有看到這個方面的知識,那就把學習相關的東西記錄下來,方便別人的學習,也對自己后面知識的鞏固和溫習有幫助。

首先給大家看看使用到堆疊式布應用的場景,

圖1 大街App堆疊式布應用場景
圖2 易信App堆疊式布應用場景
圖3 某個Gif設計圖實現(xiàn)效果

看完以后大家是不是還是覺得蠻酷炫的,有的其實還蠻復雜的,但是其實實現(xiàn)一個簡單的堆疊式的布局沒有想象的復雜,實際上就是自定義的layout來實現(xiàn)的,從簡單到復雜,這一篇文章呢就實現(xiàn)一個簡單一點的堆疊式的布局,讓大家對這個方面有一定的了解

首先看下最后實現(xiàn)的效果

圖4 簡單堆疊式布局的實現(xiàn)

看起來是不是感覺效果還蠻不錯的,其實實現(xiàn)起來也不復雜,很簡單

那么我們根據(jù)這個圖來分析下怎么實現(xiàn)這個效果

拋開動畫效果,我們發(fā)現(xiàn)當移動第一個item的時候后面的第二個item其實是已經(jīng)加載好了的,同樣的當?shù)诙€item移動的時候第三個也是加載好了的,什么觸發(fā)第三個加載好呢?顯然是第一個消失的時候取觸發(fā)的,這個是不是有點像ListView/RecycleView的效果,同樣實現(xiàn)呢也是用這種方式來實現(xiàn)的,可以理解為一個簡易版本的ListView/RecycleView。

接下來就一步一步實現(xiàn)上述的效果了

Step1.新建一個自定義的StackLayout與Adapter

StackLayout.Class

public class StackLayout extends FrameLayout{


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

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

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

}

StackAdapter.Class

public class StackAdapter extends ArrayAdapter{

    public StackAdapter(Context context) {
        super(context, R.layout.item_stack);
    }

    @Override
    public View getView(int position, final View convertView, final ViewGroup parent) {
        View view = convertView;
        if (view == null) {
            view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_stack, parent, false);
        }
        final String name = (String)getItem(position);
        ((TextView)view.findViewById(R.id.name)).setText(name);
        final View completeView = view.findViewById(R.id.completeCommand);
        view.setTag(name);
        completeView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                remove(name);
                notifyDataSetChanged();
            }
        });
        return view;
    }
}

Adapter的方法很簡單,就是顯示了xml里面的布局,旁邊的View按下的是時候會把當前的Item移除掉,然后notifyDataSetChanged 讓Adapter去更新

Step2.StackLayout與Adapter建立連接

首先在StackLayout中添加連接SetAdapter方法

public void setAdapter(ArrayAdapter adapter){
    if (adapter == null){
        throw  new IllegalArgumentException("adapter not null");
    }
    if (this.adapter != null){
        this.adapter.unregisterDataSetObserver(dataSetObserver);
    }

    this.adapter = adapter;
    this.adapter.registerDataSetObserver(dataSetObserver);
    viewsBuffer = new View[adapter.getCount()];
    attachChildViews();

}

這里面呢主要是兩個方法,第一個是registerDataSetObserver,另一個是attachChildViews,首先我們看一下registerDataSetObserver,這個的作用是當Adpater產(chǎn)生變化的時候會在dataSetObserver中監(jiān)聽到,我們通過這里來更新當Adpater變化時候的需要進行改變的東西

我們來看下dataSetObserver中的東西

private DataSetObserver dataSetObserver = new DataSetObserver() {
    @Override
    public void onChanged() {
        super.onChanged();
        attachChildViews();
    }
};

這里監(jiān)聽到以后,其實也是調用了attachChildViews方法,那我們看一下這里面應該做些什么

private void attachChildViews(){
    removeAllViews();
    for (int position = 0 ; position < adapter.getCount(); position ++ ){
        if (position < 2){
            viewsBuffer[position] = adapter.getView(position,viewsBuffer[position],this);
            addViewInLayout(viewsBuffer[position], 0, viewsBuffer[position].getLayoutParams());
        }
    }
    requestLayout();
}

這里主要是根據(jù)自己業(yè)務的需求去做不同的改變,因為這個小的Demo只需要加載兩個item就好了,所以這里當position小于3的時候會去從Adapter中拿view,然后把view添加到Framelayout中。

最后呢將兩者連接起來

StackLayout stackLayout = (StackLayout) findViewById(R.id.statck_layout);
final StackAdapter stackAdapter = new StackAdapter(this);
stackAdapter.add("1");
stackAdapter.add("2");
stackAdapter.add("3");
stackAdapter.add("4");
stackAdapter.add("5");
stackAdapter.add("6");
stackAdapter.add("7");
stackLayout.setAdapter(stackAdapter);

到這里呢一個簡單的堆疊式的布局其實已經(jīng)完成了

看看效果

圖5 簡單堆疊式效果一

我們可以通過hierarchyviewer來看一下布局的內容,是否StackLayout里面有兩個子的layout

圖6 StackLayout中布局層次

可以看到布局也是正確的

Step3.StackLayout添加動畫效果

其實這個部分有點感覺是贈送的,哈哈,因為其實上面兩步就已經(jīng)實現(xiàn)了

在將item添加到layout的時候給item添加onTouchListener

如下

addViewInLayout(viewsBuffer[position], 0, viewsBuffer[position].getLayoutParams());

private void initEvent(final View item)
{
    //設置item的重心,主要是旋轉的中心
    item.setPivotX(getScreenWidth(getContext()) / 2);
    item.setPivotY(getScreenHeight(getContext()) * 2);
    item.setOnTouchListener(new View.OnTouchListener() {
        float touchX, distanceX;//手指按下時的坐標以及手指在屏幕移動的距離

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    touchX = event.getRawX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    distanceX = event.getRawX() - touchX;

                    item.setRotation(distanceX * mRotateFactor);
                    //alpha scale 1~0.1
                    //item的透明度為從1到0.1
                    item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX));
                    break;
                case MotionEvent.ACTION_UP:

                    if (Math.abs(distanceX) > mLimitTranslateX) {
                        //移除view
                        removeViewWithAnim(item, distanceX < 0);
                    } else {
                        //復位
                        item.setRotation(0);
                        item.setAlpha(1);
                    }
                    break;
            }
            return true;
        }
    });
}

public void removeViewWithAnim( final View view, boolean isLeft)
{
    view.animate()
            .alpha(0)
            .rotation(isLeft ? -90 : 90)
            .setDuration(400).setListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            adapter.remove(view.getTag());
            adapter.notifyDataSetChanged();
        }
    });

}

那這里的代碼就不做解釋了,因為大家應該也都懂得是做了什么的

效果呢如下

圖7 簡單堆疊式效果二

最后呢貼上整個代碼

package com.demo.stackdemo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.Toast;

/**
 * Created by xinyuanhxy on 16/4/8.
 */
public class StackLayout extends FrameLayout{

    private ArrayAdapter adapter;

    private View[] viewsBuffer;

    private float mRotateFactor;//控制item旋轉范圍
    private double mItemAlphaFactor;//控制item透明度變化范圍

    private int mLimitTranslateX = 100;//限制移動距離,當超過這個距離的時候,刪除該item

    private DataSetObserver dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            super.onChanged();
            attachChildViews();
        }
    };


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

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

    public StackLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        int screenWidth = getScreenWidth(getContext());
        mRotateFactor = 60 * 1.0f / screenWidth;
        //左滑,透明度最少到0.1f
        mItemAlphaFactor = 0.9 * 1.0f / screenWidth / 2;
    }


    public void setAdapter(ArrayAdapter adapter){
        if (adapter == null){
            throw  new IllegalArgumentException("adapter not null");
        }
        if (this.adapter != null){
            this.adapter.unregisterDataSetObserver(dataSetObserver);
        }

        this.adapter = adapter;
        this.adapter.registerDataSetObserver(dataSetObserver);
        viewsBuffer = new View[adapter.getCount()];
        attachChildViews();

    }

    private void attachChildViews(){
        removeAllViews();
        for (int position = 0 ; position < adapter.getCount(); position ++ ){
            if (position < 2){
                viewsBuffer[position] = adapter.getView(position,viewsBuffer[position],this);
                viewsBuffer[position].setRotation(0);
                viewsBuffer[position].setAlpha(1);
                addViewInLayout(viewsBuffer[position], 0, viewsBuffer[position].getLayoutParams());
                initEvent(adapter.getView(position,viewsBuffer[position],this));
            }
        }
        requestLayout();
    }

    private void initEvent(final View item)
    {
        //設置item的重心,主要是旋轉的中心
        item.setPivotX(getScreenWidth(getContext()) / 2);
        item.setPivotY(getScreenHeight(getContext()) * 2);
        item.setOnTouchListener(new View.OnTouchListener() {
            float touchX, distanceX;//手指按下時的坐標以及手指在屏幕移動的距離

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        touchX = event.getRawX();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        distanceX = event.getRawX() - touchX;

                        item.setRotation(distanceX * mRotateFactor);
                        //alpha scale 1~0.1
                        //item的透明度為從1到0.1
                        item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX));
                        break;
                    case MotionEvent.ACTION_UP:

                        if (Math.abs(distanceX) > mLimitTranslateX) {
                            //移除view
                            removeViewWithAnim(item, distanceX < 0);
                        } else {
                            //復位
                            item.setRotation(0);
                            item.setAlpha(1);
                        }
                        break;
                }
                return true;
            }
        });
    }


    public void removeViewWithAnim( final View view, boolean isLeft)
    {
        view.animate()
                .alpha(0)
                .rotation(isLeft ? -90 : 90)
                .setDuration(400).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                adapter.remove(view.getTag());
                adapter.notifyDataSetChanged();
            }
        });

    }

    public static int getScreenWidth(Context context)
    {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.widthPixels;
    }

    public static int getScreenHeight(Context context)
    {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return outMetrics.heightPixels;
    }

}
package com.demo.stackdemo;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

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


        StackLayout stackLayout = (StackLayout) findViewById(R.id.statck_layout);

        final StackAdapter stackAdapter = new StackAdapter(this);
        stackAdapter.add("1");
        stackAdapter.add("2");
        stackAdapter.add("3");
        stackAdapter.add("4");
        stackAdapter.add("5");
        stackAdapter.add("6");
        stackAdapter.add("7");
        stackLayout.setAdapter(stackAdapter);

    }

    public class StackAdapter extends ArrayAdapter{

        public StackAdapter(Context context) {
            super(context, R.layout.item_stack);
        }

        @Override
        public View getView(int position, final View convertView, final ViewGroup parent) {
            View view = convertView;
            if (view == null) {
                view = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_stack, parent, false);
            }
            final String name = (String)getItem(position);
            ((TextView)view.findViewById(R.id.name)).setText(name);
            final View completeView = view.findViewById(R.id.completeCommand);
            view.setTag(name);
            completeView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    remove(name);
                    notifyDataSetChanged();
                }
            });
            return view;
        }
    }
}

大工告成~~~

寫在后面的幾句話

<p>
到這里呢,一個簡單的堆疊式布局就實現(xiàn)拉,但是,如果要實現(xiàn)更復雜的效果該怎么做呢?下一篇會對這方面進行詳細的講解,ps:語言組織能力一般,見諒~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容