1. 程式人生 > >Android 仿蘑菇街列表彈出和瀑布流 (ScrollView+RelativeLayout實現)

Android 仿蘑菇街列表彈出和瀑布流 (ScrollView+RelativeLayout實現)


之前看到用線性佈局寫的瀑布流,覺得不大好,自己想了另外一種方案,

(最近發現用 網頁實現瀑布流 再用WebView載入才能完美實現效果)

原理使用RelativeLayout任意定位位置  核心方法

private void addViewByMargins(RelativeLayout layout, View view, int x,
int y, int width, int height) {
     RelativeLayout.LayoutParams layout_params = null;
    layout_params = new RelativeLayout.LayoutParams(width, height);
   // padding是控制元件的內容相對控制元件的邊緣的邊距.
   // margin是控制元件邊緣相對父控制元件,或者其他控制元件的邊距.
    layout_params.setMargins(x, y, 0, 0);
    view.setLayoutParams(layout_params);
     layout.addView(view);
}

和二分割槽間演算法searchVisibleMethod 將非可視區域的View移除

時間關係使用的是粗陋的快取 但不能完美的解決記憶體溢位的存在。

仿蘑菇街列表滑出程式碼

package lxz.utils.android.template.view;

import com.cn.lxz.R;

import lxz.utils.android.anim.EasingType;
import lxz.utils.android.anim.ElasticInterpolator;
import lxz.utils.android.resource.AndroidUtils;
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.GestureDetector.OnGestureListener;
import android.view.View.OnTouchListener;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
import android.widget.RelativeLayout;

public class LikeMogujie extends RelativeLayout {

	public static enum State {
		// 關閉
		close,
		// 關閉中
		closeing,
		// 開啟
		open,
		// 開啟中
		opening;
	}

	// 當前狀態
	private State state;

	private int width;
	private int height;
	private int move;

	// 邊界陰影寬度
	private int bound_width;

	GestureDetector mGestureDetector;

	// 動畫時間
	private int animTime = 600;

	// 手勢距離
	private int gd_distance = 250;
	// 右邊邊距陰影圖片
	private View bound;
	// 抽屜 主內容
	private View panel;
	// 選單分類
	private View menu;

	public LikeMogujie(Context context, AttributeSet attrs) {
		super(context, attrs);
		state = state.close;
		this.post(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub

				panel = findViewById(R.id.panel);
				bound = findViewById(R.id.bound);
				menu = findViewById(R.id.menu);

				height = getMeasuredHeight();
				width = getMeasuredWidth();

				bound_width = bound.getMeasuredWidth();

				move = (int) (width / 2.05);

				mGestureDetector = new GestureDetector(getContext(),
						mOnGestureListener);
				mGestureDetector.setIsLongpressEnabled(false);
				// 對滑動欄 內容進行監聽手勢
				panel.setOnTouchListener(mOnTouchListener);

				// 將bound隱藏
				TranslateAnimation animation_bound;
				animation_bound = new TranslateAnimation(0, bound_width, 0, 0);
				animation_bound.setDuration(0);
				animation_bound.setFillAfter(true);
				bound.startAnimation(animation_bound);

				// 將分類列表隱藏
				menu.setVisibility(View.GONE);
			}
		});
	}

	private void change(State will) {
		RelativeLayout.LayoutParams layout_params = null;
		TranslateAnimation animation_bound = null;
		TranslateAnimation animation = null;
		switch (will) {
		case open:
			state = state.opening;
			menu.setVisibility(View.VISIBLE);
			layout_params = new RelativeLayout.LayoutParams(width, height);
			layout_params.setMargins(-move, 0, 0, 0);
			panel.setLayoutParams(layout_params);
			removeView(panel);
			addView(panel);
			animation_bound = new TranslateAnimation(bound_width, -move
					+ bound_width, 0, 0);
			animation = new TranslateAnimation(move, 0, 0, 0);
			break;
		case close:
			state = state.closeing;
			layout_params = new RelativeLayout.LayoutParams(width, height);
			layout_params.setMargins(0, 0, 0, 0);
			panel.setLayoutParams(layout_params);
			removeView(panel);
			addView(panel);
			animation_bound = new TranslateAnimation(-move + bound_width,
					bound_width, 0, 0);
			animation = new TranslateAnimation(-move, 0, 0, 0);

			break;
		default:
			break;
		}
		// 中斷事件訊號
		SystemClock.sleep(0);
		// 重新整理檢視
		invalidate();
		animation_bound.setDuration(animTime);
		animation_bound.setFillAfter(true);
		animation.setDuration(animTime);

		panel.startAnimation(animation);
		bound.startAnimation(animation_bound);
		animation.setAnimationListener(mAnimationListener);

	}

	private OnTouchListener mOnTouchListener = new OnTouchListener() {

		@Override
		public boolean onTouch(final View v, MotionEvent event) {
			// TODO Auto-generated method stub

			return mGestureDetector.onTouchEvent(event) || true;

		}
	};

	private AnimationListener mAnimationListener = new AnimationListener() {

		@Override
		public void onAnimationStart(Animation animation) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onAnimationRepeat(Animation animation) {
			// TODO Auto-generated method stub

		}

		@Override
		public void onAnimationEnd(Animation animation) {
			// TODO Auto-generated method stub
			if (state == state.closeing) {
				state = State.close;
				menu.setVisibility(View.GONE);
			} else if (state == state.opening) {
				state = State.open;
			}
		}
	};

	private OnGestureListener mOnGestureListener = new OnGestureListener() {

		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			if (state == State.open) {
				change(state.close);
			}
			return false;
		}

		@Override
		public void onShowPress(MotionEvent e) {
			if (state == State.open) {
				change(state.close);
			}
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
			return false;
		}

		@Override
		public void onLongPress(MotionEvent e) {
			// TODO Auto-generated method stub
			System.out.println("onLongPress" + e);
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {

			AndroidUtils.showToast(getContext(), "" + velocityX);
			if ((velocityX > 0 ? velocityX : velocityX * -1) < gd_distance) {
				return false;
			}else
			{
				if (state == state.close && velocityX < 0) {
					change(state.open);
				}
			}
			if (state == State.open) {
				change(state.close);
			}
			return false;
		}

		@Override
		public boolean onDown(MotionEvent e) {
			// TODO Auto-generated method stub
			return false;
		}
	};
	
	//開啟或者關閉
	public void OpenOrClose() {

		if (state == state.close) {
			change(state.open);
			return;
		}
		if (state == State.open) {
			change(state.close);
			return;
		}
	}
	
	


瀑布流程式碼

package com.cn.lxz.pubo2;

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.WeakHashMap;

import com.cn.lxz.R;

import lxz.utils.android.layout.LayoutUtils;
import lxz.utils.android.layout.Margins;
import lxz.utils.android.network.HttpResourcesTask;
import lxz.utils.android.network.HttpResourcesTask.CacheType;
import lxz.utils.android.network.Task;
import lxz.utils.android.network.Task.OnFinishListen;
import lxz.utils.android.network.TaskGroupAsyn;
import lxz.utils.android.network.HttpResourcesTask.HttpType;
import lxz.utils.android.network.TaskGroup.OnGroupFinsh;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class MyScrollView extends ScrollView {

	public interface OnScrollListener {
		void onBottom(int scrollY);

		void onTop(int scrollY);

		void onScroll(int scrollY);

		void onAutoScroll(int scrollY);

		void onUpdata(int scrollY);

		// 上滑
		void onScrollUP(int scrollY);

		// 下滑
		void onScrollDOWN(int scrollY);
	}

	private OnScrollListener onScrollListener;

	private AssetManager asset_manager = null;
	private List<String> image_filenames = null;
	private final String image_path = "images";

	private RelativeLayout relatLayout;
	private ProgressBar loadingbar;

	private Button flag1;
	private Button flag2;

	// 是否執行載入資料
	private boolean canLoading = true;

	// WeakHashMap<String, SoftReference<Bitmap>> map = new WeakHashMap<String,
	// SoftReference<Bitmap>>();
	// WeakHashMap<String, SoftReference<Bitmap>> map = new WeakHashMap<String,
	// SoftReference<Bitmap>>();

	// 三列
	int lineSize = 3;
	// 邊距係數 採用係數可以控制不同螢幕的尺寸問題
	float disCoeffic = 0.015f;
	float distanX;

	// 計算邊距
	int scalewidth;

	// 獲得可視區域最大值
	int maxVisibleHeight;

	// 獲得自定義佈局的高度
	int layoutHeight;
	// 獲得自定義佈局的寬度
	int layoutWidth;

	// 獲得最高的列的索引
	int maxRowIndex;
	// 獲得最高的列的高度
	int maxRowHeight;

	// 判斷向上滑true還是向下滑false
	boolean orientation;
	// 當前滑動的座標Y索引
	int scrolledIndex = 0;

	// 快速區間搜尋
	int[][] quickSearch = new int[lineSize][2];

	LinkedList<ViewInfo>[] lists = new LinkedList[lineSize];

	// 計算高度座標值
	int[] lineHeight = new int[lineSize];

	// 計算X座標值
	int[] lineX = new int[lineSize];

	{
		for (int i = 0; i < lineSize; i++) {
			lineHeight[i] = 0;
			lists[i] = new LinkedList<MyScrollView.ViewInfo>();
			lineX[i] = 103 * i;

		}
	}

	Integer tasksOver = 0;
	int tasksLength;

	// 載入網路圖片
	public void addUrlata(final List<String> list) {
		canLoading = false;
		this.post(new Runnable() {

			@Override
			public void run() {
				// Task類是未開發完全的框架 ,這裡用來載入圖片,如果必要你可以重寫該方法
				removeLoadingBar();

				tasksLength = list.size();
				tasksOver = 0;

				for (String s : list) {
					new HttpResourcesTask(getContext(), HttpType.Img,
							CacheType.saveInSDcard).setParameter(s)
							.setOnFinishListen(new OnFinishListen() {

								@Override
								public void OnFinish(Task t, Object data) {
									// TODO Auto-generated method stub
									if (data != null
											&& !(data instanceof Exception)) {
										addSingleView(t.getParameter()
												.toString(), (Drawable) t
												.getResult());
									}
									synchronized (tasksOver) {
										tasksOver++;
										if (tasksOver == tasksLength) {
											canLoading = true;
										}
									}
								}
							}).start();
				}

			}
		});
	}

	// 移除載入進度條
	private void removeLoadingBar() {

		if (loadingbar != null) {
			relatLayout.removeView(loadingbar);
			loadingbar = null;

		}
	}

	public MyScrollView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		// 初始化介面
		init();
	}

	private void init() {
		// 隱藏滾動條
		setVerticalScrollBarEnabled(false);
		relatLayout = new RelativeLayout(getContext());
		ScrollView.LayoutParams layout_params = null;
		layout_params = new ScrollView.LayoutParams(LayoutParams.FILL_PARENT,
				LayoutParams.FILL_PARENT);
		this.addView(relatLayout);

		// layout_params = new ScrollView.LayoutParams(LayoutParams.FILL_PARENT,
		// 1000);
		// padding是控制元件的內容相對控制元件的邊緣的邊距.
		// margin是控制元件邊緣相對父控制元件,或者其他控制元件的邊距.
		relatLayout.setLayoutParams(layout_params);

		// view標記主要是防止回收圖片時 relatLayout高度減少
		flag1 = new Button(getContext());
		addViewByMargins(relatLayout, flag1, 0, 0, 1, 1);
		flag2 = new Button(getContext());

		this.post(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				maxVisibleHeight = MyScrollView.this.getMeasuredHeight();
				// System.out.println("高度" +
				// MyScrollView.this.getMeasuredHeight());
			}
		});

		// 初始化分配空間的引數
		relatLayout.post(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				layoutHeight = relatLayout.getMeasuredHeight();
				layoutWidth = relatLayout.getMeasuredWidth();
				distanX = layoutWidth * disCoeffic;
				scalewidth = (int) (1.0f * (layoutWidth - (distanX * (lineSize + 1))) / lineSize);

				for (int i = 0; i < lineSize; i++) {
					lineX[i] = (int) (distanX + (distanX + scalewidth) * i);
				}

			}
		});

	}

	private void addViewByMargins(RelativeLayout layout, View view, int x,
			int y, int width, int height) {

		RelativeLayout.LayoutParams layout_params = null;
		layout_params = new RelativeLayout.LayoutParams(width, height);
		// padding是控制元件的內容相對控制元件的邊緣的邊距.
		// margin是控制元件邊緣相對父控制元件,或者其他控制元件的邊距.
		layout_params.setMargins(x, y, 0, 0);
		view.setLayoutParams(layout_params);
		layout.addView(view);

	}

	// // 獲得插入的列的索引
	// int maxRowIndex;
	private int getRowIndex() {
		int lineIndex = 0;
		for (int i = 1; i < lineSize; i++) {
			if (lineHeight[lineIndex] > lineHeight[i]) {
				lineIndex = i;
			}
		}
		maxRowIndex = lineIndex;
		return lineIndex;
	}

	private void addSingleView(String url, Drawable d) {
		try {

			// 計算獲得哪個列的高度
			int lineIndex = getRowIndex();
			int sWidth = d.getIntrinsicWidth();
			;
			float scale = 1.0f * scalewidth / sWidth;
			int height = (int) (d.getIntrinsicHeight() * scale);
			ImageView imageView = new ImageView(getContext());

			addViewByMargins(relatLayout, imageView, lineX[lineIndex],
					lineHeight[lineIndex], scalewidth, height);
			imageView.setImageDrawable(d);

			ViewInfo viewInfo = new ViewInfo(lineX[lineIndex],
					lineHeight[lineIndex], lineHeight[lineIndex] + height,
					scalewidth, height, imageView, url);
			// 加入到佇列資訊中
			lists[lineIndex].add(viewInfo);
			imageView.setTag(viewInfo);
			imageView.setOnTouchListener(childOnTouListen);

			// 高度=圖片高度+邊距
			lineHeight[lineIndex] += height + distanX;

			// 最底部插入一個flag2 view防止容器被回收
			if (lineHeight[lineIndex] > maxRowHeight) {
				maxRowHeight = lineHeight[lineIndex];
				insertFlag2(lineX[lineIndex], lineHeight[lineIndex]);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private void insertFlag2(int x, int y) {
		// TODO Auto-generated method stub
		try {
			try {
				relatLayout.removeView(flag2);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			flag2.setText("測試");
			addViewByMargins(relatLayout, flag2, x, y, 1, 1);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	@Override
	public boolean arrowScroll(int direction) {
		// TODO Auto-generated method stub
		return super.arrowScroll(direction);

	}

	@Override
	public boolean fullScroll(int direction) {
		// TODO Auto-generated method stub
		return super.fullScroll(direction);
	}

	protected void onOverScrolled(int scrollX, final int scrollY,
			boolean clampedX, boolean clampedY) {
		// TODO Auto-generated method stub

		orientation = scrollY - scrolledIndex > 0 ? false : true;
		scrolledIndex = scrollY;

		searchVisibleMethod(orientation, scrollY);

		if (onScrollListener != null) {
			if (orientation) {
				onScrollListener.onScrollUP(scrollY);
			} else {
				onScrollListener.onScrollDOWN(scrollY);
			}

			if (scrollY <= 0) {
				onScrollListener.onTop(scrollY);
			} else if (relatLayout.getMeasuredHeight()
					- this.getMeasuredHeight() - scrollY <= 0) {
				onScrollListener.onBottom(scrollY);
				// Log.e("資料", relatLayout.getMeasuredHeight() + " "
				// +this.getMeasuredHeight() + " "+scrollY );
				// 滑動到底部時載入 進度到底部
				if (loadingbar == null) {
					loadingbar = new ProgressBar(getContext());
					LayoutUtils.addView(relatLayout,
							android.view.ViewGroup.LayoutParams.FILL_PARENT,
							android.view.ViewGroup.LayoutParams.FILL_PARENT,
							loadingbar,
							new Margins(0, relatLayout.getMeasuredHeight(), 0,
									0), null);
				}
				if (canLoading) {
					canLoading = false;
					new Thread() {

						@Override
						public void run() {
							try {
								sleep(1000);
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							if (onScrollListener != null) {
								onScrollListener.onUpdata(scrollY);
							}

						}

					}.start();

				}
			} else {
				onScrollListener.onScroll(scrollY);
			}
		}
		super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
	}

	public OnScrollListener getOnScrollListener() {
		return onScrollListener;
	}

	public void setOnScrollListener(OnScrollListener onScrollListener) {
		this.onScrollListener = onScrollListener;
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		// TODO Auto-generated method stub
		// System.out.println("onSizeChanged=" + "W=" + w + "  H=" + h +
		// "  oldw="
		// + oldw + "  clampedY" + oldh);
		super.onSizeChanged(w, h, oldw, oldh);
	}

	@Override
	public boolean pageScroll(int direction) {
		// TODO Auto-generated method stub
		System.out.println(" pageScroll=" + direction);
		return super.pageScroll(direction);
	}

	// 演算法 防止Out of Memory 通過方向和區間搜尋快速移除非可視區域的view
	private void searchVisibleMethod(boolean orientation, int scrollY) {
		for (int k = 0; k < lineSize; k++) {
			// 向上滑
			int length = lists[k].size();
			// 初始化區間索引 quickSearch[k][0]
			// 標示k列的可視區域的從上向下第一個檢視
			// quickSearch[k][1]可視區域的從下向上第一個檢視
			if (quickSearch[k] == null) {
				quickSearch[k] = new int[2];
				quickSearch[k][0] = 0;
				quickSearch[k][1] = length;
			}

			// 二分搜尋區間搜尋
			if (orientation) {
				// 開關從滿足條件的地方開始索引
				boolean indexonOff = true;
				// 正向查詢
				for (int i = quickSearch[k][1]; i >= 0; i--) {

					ViewInfo v = lists[k].get(i);
					// 是否在可視範圍內部
					if (isViewinVisible(v, scrollY)) {
						// Log.e("shua",""+ quickSearch[k][1]);
						if (indexonOff == true) {
							quickSearch[k][1] = i;
							indexonOff = false;
						}
						quickSearch[k][1] = i;
						if (v.isIn == false) {
							try {
								// Log.e("shua", "" + "AAAAA");
								v.addView();
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							v.isIn = true;
						}

					} else {
						try {
							if (indexonOff == false) {
								quickSearch[k][0] = i;
								// System.out.println(quickSearch[k][1]);
							}
							if (v.isIn == true) {
								relatLayout.removeView(v.getView());
							}
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						v.isIn = false;
					}
				}

			} else {
				// 開關從滿足條件的地方開始索引
				boolean indexonOff = true;
				// 反向查詢
				for (int i = quickSearch[k][0]; i < length; i++) {
					ViewInfo v = lists[k].get(i);
					// 是否在可視範圍內部
					if (isViewinVisible(v, scrollY)) {
						if (indexonOff == true) {
							quickSearch[k][0] = i;
							indexonOff = false;
						}
						quickSearch[k][0] = i;
						if (v.isIn == false) {
							try {
								v.addView();
							} catch (Exception e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							v.isIn = true;
						}

					} else {
						try {
							if (indexonOff == false) {
								quickSearch[k][1] = i;
							}
							if (v.isIn == true) {
								relatLayout.removeView(v.getView());
							}
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						v.isIn = false;
					}

				}
			}

		}
	}

	// 是否在可視區域
	private boolean isViewinVisible(ViewInfo v, int scrollY) {

		return (v.y1 >= scrollY && v.y1 <= scrollY + maxVisibleHeight)
				|| (v.y2 >= scrollY && v.y2 <= scrollY + maxVisibleHeight)
				|| (v.y1 <= scrollY && v.y2 >= scrollY + maxVisibleHeight);

	}

	public class ViewInfo {
		private int x;
		private int y1;
		private int y2;
		private int width;
		private int height;
		private SoftReference<ImageView> soft;
		private String url;
		private boolean isIn = true;

		public ViewInfo(int x, int y1, int y2, int width, int height,
				ImageView view, String url) {
			super();
			this.x = x;
			this.y1 = y1;
			this.y2 = y2;
			this.width = width;
			this.height = height;
			soft = new SoftReference<ImageView>(view);
			this.url = url;
		}

		public String getUrl() {
			return url;
		}

		public View getView() {
			if (soft != null && soft.get() != null) {
				return soft.get();
			}
			return null;
		}

		public View addView() {

			ImageView iv = soft.get();
			if (iv != null) {

				try {
					addViewByMargins(relatLayout, iv, x, y1, width, height);
					return iv;
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} finally {
					iv = null;
				}
			}

			new HttpResourcesTask(getContext(), HttpType.Img,
					CacheType.saveInSDcard).setParameter(url)
					.setOnFinishListen(new OnFinishListen() {

						@Override
						public void OnFinish(Task task, Object data) {
							// TODO Auto-generated method stub
							Drawable drawable = (Drawable) data;
							ImageView imageView = new ImageView(getContext());
							addViewByMargins(relatLayout, imageView, x, y1,
									width, height);
							soft = new SoftReference<ImageView>(imageView);
							imageView.setImageDrawable(drawable);
							imageView.setTag(ViewInfo.this);
							imageView.setOnTouchListener(childOnTouListen);

						}
					}).start();

			return null;

		}

	}

	OnTouchListener childOnTouListen = new OnTouchListener() {

		@Override
		public boolean onTouch(View v, MotionEvent event) {
			// TODO Auto-generated method stub
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(getContext(), ((ViewInfo) v.getTag()).getUrl(),
						1).show();
			}
			return false;
		}
	};
}