首页 热点资讯 义务教育 高等教育 出国留学 考研考公
您的当前位置:首页正文

Android 堆叠式布局实现(一)简单堆叠式布局

2024-12-18 来源:化拓教育网

写在前面的几句话

<p>
堆叠式布局,顾明思议,堆在一起的布局,我百度了一下好像还真没有太多关于这个方面的讲解,刚好最近学习的时候有看到这个方面的知识,那就把学习相关的东西记录下来,方便别人的学习,也对自己后面知识的巩固和温习有帮助。

首先给大家看看使用到堆叠式布应用的场景,

图1 大街App堆叠式布应用场景 图2 易信App堆叠式布应用场景 图3 某个Gif设计图实现效果

看完以后大家是不是还是觉得蛮酷炫的,有的其实还蛮复杂的,但是其实实现一个简单的堆叠式的布局没有想象的复杂,实际上就是自定义的layout来实现的,从简单到复杂,这一篇文章呢就实现一个简单一点的堆叠式的布局,让大家对这个方面有一定的了解

首先看下最后实现的效果

图4 简单堆叠式布局的实现

看起来是不是感觉效果还蛮不错的,其实实现起来也不复杂,很简单

那么我们根据这个图来分析下怎么实现这个效果

抛开动画效果,我们发现当移动第一个item的时候后面的第二个item其实是已经加载好了的,同样的当第二个item移动的时候第三个也是加载好了的,什么触发第三个加载好呢?显然是第一个消失的时候取触发的,这个是不是有点像ListView/RecycleView的效果,同样实现呢也是用这种方式来实现的,可以理解为一个简易版本的ListView/RecycleView。

接下来就一步一步实现上述的效果了

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.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产生变化的时候会在dataSetObserver中监听到,我们通过这里来更新当Adpater变化时候的需要进行改变的东西

我们来看下dataSetObserver中的东西

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

这里监听到以后,其实也是调用了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();
}

这里主要是根据自己业务的需求去做不同的改变,因为这个小的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);

到这里呢一个简单的堆叠式的布局其实已经完成了

看看效果

图5 简单堆叠式效果一

我们可以通过hierarchyviewer来看一下布局的内容,是否StackLayout里面有两个子的layout

图6 StackLayout中布局层次

可以看到布局也是正确的

Step3.StackLayout添加动画效果

其实这个部分有点感觉是赠送的,哈哈,因为其实上面两步就已经实现了

在将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.setTag(name);
            completeView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    remove(name);
                    notifyDataSetChanged();
                }
            });
            return view;
        }
    }
}

大工告成~~~

写在后面的几句话

<p>
到这里呢,一个简单的堆叠式布局就实现拉,但是,如果要实现更复杂的效果该怎么做呢?下一篇会对這方面进行详细的讲解,ps:语言组织能力一般,见谅~~~

显示全文