在博文
与之前基本一样,只是多了一种不乱序的显示控制。
package com.kedi.mylayout.mode; import android.graphics.Bitmap; /** * 切片实体类 * * @author 张科勇 * */ public class Slice { private int index;// 切片索引值 private Bitmap bitmap;// 切片图片对象 public Slice() { } public Slice(int index, Bitmap bitmap) { super(); this.index = index; this.bitmap = bitmap; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public Bitmap getBitmap() { return bitmap; } public void setBitmap(Bitmap bitmap) { this.bitmap = bitmap; } @Override public String toString() { return "Slice [index=" + index + ", bitmap=" + bitmap + "]"; } }
package com.kedi.mylayout.utils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import com.kedi.mylayout.mode.Slice; import android.graphics.Bitmap; /** * 图片切片工具类 * * @author 张科勇 * */ public class SliceUtil { /** * 切割图片的方法,切成slices行 *slices列 个图片, * * @param bitmap * 要切割的图片对象 * @param slices * 要切割的列数, * @return 将切割后的slices行 *slices列 个图片封装到List<Slice>中并返回 */ public static List<Slice> splitPic(Bitmap bitmap, int slices) { List<Slice> sliceList = new ArrayList<Slice>(); if (slices >= 1) { // 获得要切割的图片的宽高 int width = bitmap.getWidth(); int height = bitmap.getHeight(); // 得到每个切片图片的宽高,这里让宽高一样,意思是切成了正方形 int sliceWH = Math.min(width, height) / slices; // 开始切割,使用双循环,切割成slices行,slices列 for (int i = 0; i < slices; i++) { for (int j = 0; j < slices; j++) { /* * 把当前行列号作为切片的索引值,假如slices=3 * ================== * 0+0,0+1,0+2 * 3+0,3+1,3+2 * 6+0,6+1,6+2 * ================== * 0,1,2 * 3,4,5 * 6,7,8 * ================== */ int index = i * slices + j; // 切片Bitmap对应的x,y坐标,x由列决定,y则行决定 int x = j * sliceWH; int y = i * sliceWH; Bitmap sliceBitmap = Bitmap.createBitmap(bitmap, x, y, sliceWH, sliceWH); // 创建切片对象,并把索引值和切片Bitmap封装到切片对象中 Slice slice = new Slice(index, sliceBitmap); // 将每个切片对象保存到List集合中去 sliceList.add(slice); } } } // 返回切片对象 return sliceList; } /** * 随机打乱List集合中的对象 Moves every element of the list to a random new position * in the list. * * @param slideList * 要打乱顺序的List集合 * @return 返回一个打乱了顺序的List集合 */ public static List<Slice> shuffleList1(List<Slice> sliceList) { Collections.shuffle(sliceList); return sliceList; } /** * 随机打乱List集合中的对象 Moves every element of the list to a random new position * in the list. * * @param slideList * 要打乱顺序的List集合 * @return 返回一个打乱了顺序的List集合 */ public static List<Slice> shuffleList2(List<Slice> sliceList) { Collections.sort(sliceList, new Comparator<Slice>() { @Override public int compare(Slice s1, Slice s2) { // 正常的比较是s1>s2 返回1,s1<s2 返回-1,s1=s2返回0 // 这里我们返回一个不确定的(-1,1,0),这样就可以把顺序打乱 double random = Math.random(); if (random == 0.5) { return 0; } else if (random > 0.5) { return 1; } else { return -1; } } }); return sliceList; } }
package com.kedi.mylayout.utils; import android.content.Context; /** * dp与px转换工具,为屏幕适配 * * @author 张科勇 * */ public class DensityUtil { /** * 从 dp转为px(像素) */ public static int dip2px(Context context, float dp) { // return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); final float density = context.getResources().getDisplayMetrics().density; return (int) (dp * density + 0.5f); } /** * 从 px(像素)转为 dp */ public static int px2dip(Context context, float px) { final float density = context.getResources().getDisplayMetrics().density; return (int) (px / density + 0.5f); } }
package com.kedi.mylayout.views; import com.kedi.mylayout.R; import android.content.Context; import android.widget.RelativeLayout; /** * 自定义布局 * * @author 张科勇 * */ public class MyLayout extends RelativeLayout { public MyLayout(Context context) { this(context, null); } public MyLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } /** * 初始化方法 */ private void init(Context context, AttributeSet attrs) { } }(3)定义一些预见的成员变量。
// 要切割的图片Bitmap对象 private Bitmap mPic; // 要显示切片的行列数,默认显示整张图片,所以行列数为1 private int mRowNum = 1; // 切割后得到的切片实体对象集合 private List<Slice> mSliceList; // 是否需要乱序切片顺序的boolean变量 private boolean isNoOrder = true; // 存放切片ImageView的数组 private ImageView[] sliceArray; // 自定义布局宽高 private int mWidth; // 切片ImageView的宽高 private int mSliceViewWidth; // 容器内边距 private int padding; // 切片ImageView的外边距 private int margin;(4)在init()初始化方法中初始化目前可以有值的成员变量。
/** * 初始化方法 */ private void init(Context context, AttributeSet attrs) { // 准备切割的图片对象 if (mPic == null) { mPic = BitmapFactory.decodeResource(getResources(), R.drawable.pic); } // 初始化切片View的外边距 margin = DensityUtil.dip2px(getContext(), 1); // 获得XMl布局中的内边距,并把左、右、上、下中最小的内边距离做为容器的内边距 padding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); // 如果没有在XML中使用padding设置,当代码给一个和margin一样大小的padding值 padding = (padding == 0) ? DensityUtil.dip2px(getContext(), 1) : padding; } /** * 获得最小的距离做为容器的内边距 * * @param paddings * @return */ private int min(int... paddings) { int min = paddings[0]; for (int padding : paddings) { if (padding < min) { min = padding; } } return min; }(5)自定义属性rawNum。目的是为了可以在XML中使用自定义属性指定图片要切割行列数。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="myLayout"> <attr name="rowNum" format="integer" /> </declare-styleable> </resources>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.kedi.mylayout.views.MyLayout android:id = "@+id/my_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="1dp" app:rowNum="3" > </com.kedi.mylayout.views.MyLayout> <SeekBar android:id = "@+id/sb_progress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0" android:max="40" android:progress="3" android:layout_margin="20dp" /> </LinearLayout>
xmlns:app="http://schemas.android.com/apk/res-auto"
/** * 初始化方法 */ private void init(Context context, AttributeSet attrs) { // 获得自定义属性,并将XML中设置的行列数获得到赋值给mRowNum TypedArray t = context.obtainStyledAttributes(attrs, R.styleable.myLayout); mRowNum = t.getInt(R.styleable.myLayout_rowNum, mRowNum); mRowNum= mRowNum<=0?1:mRowNum; //释放TypedArray对象 t.recycle(); // 准备切割的图片对象 if (mPic == null) { mPic = BitmapFactory.decodeResource(getResources(), R.drawable.pic); } // 初始化切片View的外边距 margin = DensityUtil.dip2px(getContext(), 1); // 获得XMl布局中的内边距,并把左、右、上、下中最小的内边距离做为容器的内边距 padding = min(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom()); // 如果没有在XML中使用padding设置,当代码给一个和margin一样大小的padding值 padding = (padding == 0) ? DensityUtil.dip2px(getContext(), 1) : padding; } /** * 获得最小的距离做为容器的内边距 * * @param paddings * @return */ private int min(int... paddings) { int min = paddings[0]; for (int padding : paddings) { if (padding < min) { min = padding; } } return min; }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); // 布局的宽高设置为设备屏幕的宽 setMeasuredDimension(mWidth, mWidth); }
/** * 切割图片 */ private void splitPic() { // 切割图片,得到集合 mSliceList = SliceUtil.splitPic(mPic, mRowNum); // 判断是否需要乱序显示切片 if (isNoOrder) { mSliceList = SliceUtil.shuffleList1(mSliceList); } }既然乱序可以对外控制,那我们应该对外提供一个控制方法。
/** * 设置切片对象是否乱序 * @param isNoOrder */ public void setNoOrder(boolean isNoOrder) { this.isNoOrder = isNoOrder; }(8)设计生成切片View、显示切片图片,对切片View其进行布局的方法。(这块可能是个难点,详细注释)
/** * 生成并布局切片View,并把切片显示到切片View上 */ private void generateAndLayoutSliceView() { // 获得切片View的宽高 mSliceViewWidth = (mWidth - (padding * 2) - (margin * (mRowNum - 1))) / mRowNum; // 生成和布局显示切片的ImageView,并显示切片 sliceArray = new ImageView[mRowNum * mRowNum]; for (int i = 0; i < sliceArray.length; i++) { ImageView sliceView = new ImageView(getContext()); Slice slice = mSliceList.get(i); // 显示切片 sliceView.setImageBitmap(slice.getBitmap()); // 为ImageView设置id,为了可以在相对布局的相对布局使用 sliceView.setId(0x00001 + i); // 将切片View保存到对应的数据中 sliceArray[i] = sliceView; // 定位ImageView显示的位置 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(mSliceViewWidth, mSliceViewWidth); // 列布局(判断除第一列外,其它切片View都在前一个切片View的右边) if (i % mRowNum != 0) { params.addRule(RelativeLayout.RIGHT_OF, sliceArray[i - 1].getId()); } // 判断除最后一列外,其它切片View都的一个大小为margin的的右外边距 if ((i + 1) % mRowNum != 0) { params.rightMargin = margin; } // 行布局(判断除第一行外,其它切片View都在上一行同列切片View的下边) if (i > mRowNum - 1) { params.addRule(RelativeLayout.BELOW, sliceArray[i - mRowNum].getId()); // 判断除第一行外,其它行的切片View与上一行同列的切片View有上外边距margin params.topMargin = margin; } sliceView.setLayoutParams(params); // 上面确定了将切片View的大小和位置后就可以添加到容器中了 addView(sliceView); } }(9)设计一个调用(6)和(7)步的总方法,因为这两个步骤的逻辑可能会在多个地方重复使用。
/** * 流程控制方法(统一切割图片和生成切片View的逻辑) * @param rowNum */ public void showSliceViews(int rowNum){ this.mRowNum = rowNum; int childCount = getChildCount(); if(childCount>0){ removeAllViews(); } // 切割图片 splitPic(); // 生成并布局切片View,并把切片显示到切片View上 generateAndLayoutSliceView(); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth()); // 布局的宽高设置为设备屏幕的宽 setMeasuredDimension(mWidth, mWidth); if(once){ showSliceViews(mRowNum); once = false; } }
package com.kedi.mylayout; import com.kedi.mylayout.views.MyLayout; import android.app.Activity; import android.os.Bundle; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; /** * MainActivty类 * @author 张科勇 * */ public class MainActivity extends Activity { private MyLayout myLayout; private SeekBar mProgressSb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } /** * 初始化的方法 */ private void init() { initDatas(); initViews(); initEvents(); myLayout.setNoOrder(false); } /** * 初始化数据的方法 */ private void initDatas() { } /** * 初始化View的方法 */ private void initViews() { myLayout = (MyLayout) findViewById(R.id.my_layout); mProgressSb = (SeekBar) findViewById(R.id.sb_progress); } /** * 初始化事件的方法 */ private void initEvents() { mProgressSb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { myLayout.setNoOrder(false); myLayout.showSliceViews(progress); } }); } }