Android 仿蘑菇街列表彈出和瀑布流 (ScrollView+RelativeLayout實現)
阿新 • • 發佈:2019-02-04
之前看到用線性佈局寫的瀑布流,覺得不大好,自己想了另外一種方案,
(最近發現用 網頁實現瀑布流 再用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; } }; }