Android 사용자 정의 ViewGroup 의 WaterfallLayout(2)

이전 편 에 서 는 사용자 정의 ViewGroup 의 기본 절 차 를 배 웠 고 사용자 정의 GridLayout 의 인 스 턴 스 를 만 들 었 습 니 다.이 편 에 서 는 사용자 정의 ViewGroup 에 대해 계속 이야기 합 니 다.
Android 에서 많은 사진 을 보 여 줘 야 할 때 우 리 는 GridView 를 사진 벽 으로 사용 할 수 있 습 니 다.그러나 GridView 는 너무 가지런 하고 불규칙 한 것 도 아름 답 습 니 다.폭포 흐름 모델 은 바로 이런 불규칙 한 전시 벽 입 니 다.그 다음 에 우 리 는 사용자 정의 ViewGroup 으로 폭포 흐름 을 실현 하려 고 합 니 다.
폭포 류 를 실현 하 는 방식 도 많 습 니 다.다음은 우리 가 하나씩 말씀 드 리 겠 습 니 다.
1.ViewGroup 계승
사실 이런 실현 방식 은 우 리 는 위의 블 로 그 를 바탕 으로 조금 만 수정 하면 된다.주로 이 몇 가 지 를 수정 할 수 있다.
 •LayoutParams
폭포 흐름 에서 모든 그림 의 너비 가 같 고 높이 가 다 르 기 때문에 top 에 고정 높이 를 더 해서 bottom 을 얻 을 수 없 기 때문에 여기 서 나 는 아예 네 개의 매개 변 수 를 모두 정의 했다.

public static class LayoutParams extends ViewGroup.LayoutParams {
  public int left = 0;
  public int top = 0;
  public int right = 0;
  public int bottom = 0;

  public LayoutParams(Context arg0, AttributeSet arg1) {
  super(arg0, arg1);
  }

  public LayoutParams(int arg0, int arg1) {
  super(arg0, arg1);
  }

  public LayoutParams(android.view.ViewGroup.LayoutParams arg0) {
  super(arg0);
  }

 }

 •onMeasure
여 기 는 그림 마다 너비 가 같 고 높 은 비율 로 크기 를 조정 하기 때문에 Waterfall Layout 의 layotheight 소 용 없어 요.다음 에 그림 을 추가 할 때 높이 가 가장 작은 열 에 추가 할 수 있 도록 배열 top[colums]로 현재 높이 를 기록 합 니 다.

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
 measureChildren(widthMeasureSpec, heightMeasureSpec);

 int childCount = this.getChildCount();
 //    wrap_content ,childWidth childView     ,      
 if (widthMode == MeasureSpec.AT_MOST) {
  for (int i = 0; i < childCount; i++) {
  View child = this.getChildAt(i);
  childWidth = Math.max(childWidth, child.getMeasuredWidth());
  }
 } else if (widthMode == MeasureSpec.EXACTLY) {
  childWidth = (sizeWidth - (colums - 1) * hSpace) / colums;
 } 
 //   View onMeasure、onLayout     ,             
 clearTop();
 //     view,           LayoutParams ,   onLayout  
 for (int i = 0; i < childCount; i++) {
  View child = this.getChildAt(i);
  childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth();
  LayoutParams lParams = (LayoutParams) child.getLayoutParams();
  int minColum = getMinHeightColum();
  lParams.left = minColum * (childWidth + hSpace); 
  lParams.top = top[minColum];
  lParams.right = lParams.left + childWidth;  
  lParams.bottom = lParams.top + childHeight;
  top[minColum] += vSpace + childHeight;
 }
 //   wrap_content ,    viewGroup  
 int wrapWidth;
 int wrapHeight;
 if (childCount < colums) {
  wrapWidth = childCount * childWidth + (childCount - 1) * hSpace;
 } else {
  wrapWidth = colums * childWidth + (colums - 1) * hSpace;
 }
 wrapHeight = getMaxHeight();

 setMeasuredDimension(widthMode == MeasureSpec.AT_MOST? wrapWidth:sizeWidth, wrapHeight);
 }

 •onLayout
LayoutParams 는 View 의 네 개의 인 자 를 정의 하기 때문에 직접 설정 하면 됩 니 다.

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 int childCount = this.getChildCount();
 for (int i = 0; i < childCount; i++) {
  View child = this.getChildAt(i);
  LayoutParams lParams = (LayoutParams) child.getLayoutParams();
  child.layout(lParams.left, lParams.top, lParams.right, lParams.bottom);
 }
 }
여기 서 주의해 야 할 부분 이 있 습 니 다.매번 하위 View 의 Layout Params 를 설정 하기 전에 top[]배열 을 0 으로 해 야 합 니 다.onMeasure 와 onLayout 는 두 번 호출 되 기 때문에 다음 설정 매개 변수 가 정확 하도록 확보 합 니 다.
연장:왜 view Group 의 onMeasure 와 onLayout 방법 을 두 번 호출 합 니까?
왜냐하면 우리 가 new View Group()을 사용 할 때 getWidth()와 getHeight()를 통 해 얻 은 값 은 먼저 0,0 이 고 onMeasure()와 onLayout()방법 을 호출 하면 이 view 의 크기 를 측정 합 니 다.이때 view 의 너비 가 바 뀌 었 습 니 다.이 때 onMeasure 와 onLayout 방법 을 다시 호출 합 니 다(view 가 바 뀌 었 을 때,이 두 가지 방법 은 호출 될 것 이다.)이때 getWidth 와 getHeight 방법 을 통 해 측정 후의 너비 와 높이 를 볼 수 있다.이것 이 바로 두 번 호출 될 수 있 는 원인 이다.
 •클릭 이벤트 리 셋

 //         
 public interface OnItemClickListener {
 void onItemClick(View v, int index);
 }

 public void setOnItemClickListener(final OnItemClickListener listener) {
 for (int i = 0; i < getChildCount(); i++) {
  final int index = i;
  View view = getChildAt(i);
  view.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
   listener.onItemClick(v, index);
  }
  });
 }
 }

WaterfallLayout 를 사용 하여 그림 추가 하기:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.hx.waterfalllayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#303030"
 android:orientation="vertical" >

 <com.hx.waterfalllayout.WaterfallLayout
 android:id="@+id/gridview"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:background="#1e1d1d"
 app:hSpace="10"
 app:numColumns="3"
 app:vSpace="10" >

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_1" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_2" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_1" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_2" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_1" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_2" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_1" />

 <ImageView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:scaleType="centerCrop"
  android:src="@drawable/crazy_2" />
 </com.hx.waterfalllayout.WaterfallLayout>
</ScrollView>

여기 서 가장 바깥쪽 에 우리 가 사용 하 는 ScrollView 는 사진 벽 에 사진 을 무한 추가 할 수 있 기 때문에 사진 의 수량 이 주파수 막 범 위 를 초과 한 후에 굴 러 갈 수 있 도록 합 니 다.그리고 여기 ImageView 는 모두 xml 에 쓰 여 있 습 니 다.물론 자바 에서 이 View Group 에 동적 으로 ImageView 를 추가 할 수 있 고 코드 가 더욱 아름 답 습 니 다.
폭포 흐름 그림 의 클릭 이벤트 리 셋 함수 구현:

((WaterfallLayout) findViewById(R.id.waterfallLayout)).setOnItemClickListener(new com.hx.waterfalllayout.WaterfallLayout.OnItemClickListener() {
  @Override
  public void onItemClick(View v, int index) {
   Toast.makeText(MainActivity.this, "item="+index, Toast.LENGTH_SHORT).show();
  } 
  });

실행 효과 보기:

연장:
일반적으로 사용자 정의 컨트롤 은 scrollview 에 끼 워 넣 으 면 불완전 하 게 표 시 됩 니 다.이 문 제 는 매우 복잡 합 니 다.그러나 scrollview 의 소스 코드 를 열 면 scrollview 에 끼 워 넣 은 viewpager,gridview,listview 를 이해 할 수 있 는 곳 이 있 습 니 다.

여기에 포 함 된 viewpager,gridview,listview 를 완전히 표시 할 수 있 는 작은 기술 이 있 습 니 다.예 를 들 어 우 리 는 자신의 Other GridView 를 Gridview 에 계승 하고 onMeasure 방법 을 다시 쓰 면 됩 니 다.다른 View Group 은 마찬가지 입 니 다.

public class OtherGridView extends GridView {

 public OtherGridView(Context paramContext, AttributeSet paramAttributeSet) {
 super(paramContext, paramAttributeSet);
 }

 /**  ScrollView ,          */
 @Override
 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
  MeasureSpec.AT_MOST);
 super.onMeasure(widthMeasureSpec, expandSpec);
 }
}

2.계승 ScrollView
ScrollView 를 계승 하 는 폭포 흐름 모형 은 그림 이 너무 많 으 면 미끄럼 식 이 필요 합 니 다.
이 때 onMesure 를 다시 쓸 필요 가 없습니다.onLayout 만 다시 쓸 수 있 습 니 다.
 •onLayout 

 /**
 *              ,  ScrollWaterfallLayout   ,           。          
 */
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 super.onLayout(changed, l, t, r, b);
 if (changed && !loadOnce) {
  firstColumn = (LinearLayout) findViewById(R.id.first_column);
  secondColumn = (LinearLayout) findViewById(R.id.second_column);
  thirdColumn = (LinearLayout) findViewById(R.id.third_column);
  columnWidth = firstColumn.getWidth();
  loadOnce = true;
  loadImages();
 }
 }
 •그림 불 러 오기

 /**
 *       
 */
 public void loadImages() {
 for (int i = 0; i < imageRes.length; i++) {
  Bitmap bitmap = resource2Bitmap(imageRes[i]);
  if (bitmap != null) {
  double ratio = bitmap.getWidth() / (columnWidth * 1.0);
  int scaledHeight = (int) (bitmap.getHeight() / ratio);
  addImage(i, bitmap, columnWidth, scaledHeight);
  }
 }
 }

/**
 *  ImageView       
 * 
 * @param bitmap
 *        
 * @param imageWidth
 *       
 * @param imageHeight
 *       
 */
 private void addImage(int index, Bitmap bitmap, int imageWidth, int imageHeight) {
 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(imageWidth, imageHeight);
 ImageView imageView = new ImageView(getContext());
 imageView.setLayoutParams(params);
 imageView.setImageBitmap(bitmap);
 imageView.setScaleType(ScaleType.FIT_XY);
 imageView.setPadding(5, 5, 5, 5);
 findColumnToAdd(imageView, imageHeight).addView(imageView);
 //            
 imageView.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
  if (onItemClickListener != null) {
   onItemClickListener.onItemClick(v, index);
  }  
  }
 });
 }

 /**
 *              。              ,                  。
 * 
 * @param imageView
 * @param imageHeight
 * @return          
 */
 private LinearLayout findColumnToAdd(ImageView imageView,int imageHeight) {
 if (firstColumnHeight <= secondColumnHeight) {
  if (firstColumnHeight <= thirdColumnHeight) {
  firstColumnHeight += imageHeight;
  return firstColumn;
  }
  thirdColumnHeight += imageHeight;
  return thirdColumn;
 } else {
  if (secondColumnHeight <= thirdColumnHeight) {
  secondColumnHeight += imageHeight;
  return secondColumn;
  }
  thirdColumnHeight += imageHeight;
  return thirdColumn;
 }
 }

여기까지 폭포 흐름 사진 벽 을 표시 할 수 있 습 니 다.편리 하 죠?그러나 이런 방식 도 한계 가 있다.예 를 들 어 여기 열 폭 이 3 열 로 쓰 여 져 있어 확장 성 이 좋 지 않다.
코드 에서 우 리 는 사용자 정의 ViewGroup 이 모든 childView 를 실현 하 는 layot 방법 을 보지 못 했 습 니 다.그러면 childView 는 어떻게 배치 합 니까?사실 childView 의 레이아웃 은 LinearLayout 를 통 해 이 루어 집 니 다.즉,LinearLayout 내부 에서 모든 childView 의 layot 방법 을 호출 했 습 니 다.이것 은 이전에 우리 가 사용자 정의 View 를 말 했 을 때의 조합 컨트롤 과 비슷 하지 않 습 니까?
findColumnToAdd(imageView, imageHeight).addView(imageView);
 •정의 그림 클릭 리 셋 인터페이스

 //         
 public OnItemClickListener onItemClickListener;
 public interface OnItemClickListener {
 void onItemClick(View v, int index);
 }

 public void setOnItemClickListener(OnItemClickListener onItemClickListener){
 this.onItemClickListener = onItemClickListener;
 }

 •ScrollWaterfallLayout 사용 하기
코드 에 세 열 만 지정 되 어 있 기 때문에 xml 는 세 개의 수평 으로 배 치 된 LinearLayout 가 필요 합 니 다.

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">

 <com.hx.waterfalllayout.ScrollWaterfallLayout
 android:id="@+id/scrollWaterfallLayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >

 <LinearLayout
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal" >

  <LinearLayout
  android:id="@+id/first_column"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:orientation="vertical" >
  </LinearLayout>

  <LinearLayout
  android:id="@+id/second_column"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:orientation="vertical" >
  </LinearLayout>

  <LinearLayout
  android:id="@+id/third_column"
  android:layout_width="0dp"
  android:layout_height="wrap_content"
  android:layout_weight="1"
  android:orientation="vertical" >
  </LinearLayout>
 </LinearLayout>
 </com.hx.waterfalllayout.ScrollWaterfallLayout>
</LinearLayout>

폭포 흐름 그림 의 클릭 이벤트 리 셋 함수 구현:

((ScrollWaterfallLayout)findViewById(R.id.scrollWaterfallLayout)).setOnItemClickListener(new com.hx.waterfalllayout.ScrollWaterfallLayout.OnItemClickListener() {
  @Override
  public void onItemClick(View v, int index) {
   Toast.makeText(MainActivity.this, "item="+index, Toast.LENGTH_SHORT).show();
  } 
  });

실행 효과:

원본 다운로드:http://xiazai.jb51.net/201609/yuanma/Android-WaterfallLayout(jb51.net).rar
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기