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



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

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

可以看到布局也是正確的
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();
}
});
}
那這里的代碼就不做解釋了,因為大家應該也都懂得是做了什么的
效果呢如下

最后呢貼上整個代碼
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:語言組織能力一般,見諒~~~