PS-本项目由我一人独立完成,尤其是图片分割规则的计算,本来想从网上找是否由相关的代码和思路,找了一圈发现没有,后来我自己摸索出来了一套计算的规则和方法,如果大家有什么想法我们可以互相讨论。
app下载地址:小妖拼图下载地址
背景:
最早接触android是从《第一行代码》开始的,看完后我做了一个课后联系天气预报APP。
后来又慢慢啃完了《android群英传》,最后实战章节有个拼图游戏。但是书本上的联系太过简单,后来自己从网上看看别的拼图游戏,后来开发了这款小妖拼图的app。没有想到开发这个APP我自己就进入了一个大坑,拼图游戏的核心是对于图像的处理,这块的逻辑处理占用了项目的50%的时间。 期间又有一些新的想法和尝试,后来都无疾而终。
不足:
本来想做一个得分规则、排名和第三方登陆,这些需要用到后台Baas和第三方许可。而Baas服务需要花钱,被我PASS了;第三方登陆,现在申请创建移动应用都需要花钱搞软件著作权,也被我PASS。 顺便吐槽一句,现在个人开发者太难了。做什么都需要花钱,唉。
话不多说,先上图看下程序的效果。
代码梳理:
1.MyApplication.
初始化相册选择器,使用的是网上的开源的框架;初始化拼图对象池,否则在设置拼图块的时候,会不停的创建新的对象耗尽内存,导致程序很卡;初始化设置参数和相册。
public class MyApplication extends Application {
private static Context mContext;
private static int mScreenWidth;
private static int mScreenHeight;
private ImagePicker imagePicker;
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
//初始化对象池
PiecePool.getInstance();
mScreenWidth = ScreenUtil.getScreenWidth(mContext);
mScreenHeight = ScreenUtil.getScreenHeight(mContext);
//
//初始化相册选择参数
imagePicker = ImagePicker.getInstance();
imagePicker.setImageLoader(new PicassoImageLoader());
imagePicker.setMultiMode(false);
imagePicker.setStyle(CropImageView.Style.RECTANGLE);
int width = 0;
int height = 0;
if (mScreenHeight > mScreenWidth) {
width = mScreenWidth;
} else {
width = mScreenHeight;
}
height = width;
imagePicker.setFocusWidth(width);
imagePicker.setFocusHeight(height);
imagePicker.setOutPutX(width);
imagePicker.setOutPutY(height);
imagePicker.setShowCamera(true);
imagePicker.setCrop(true);
imagePicker.setSaveRectangle(true);
//只有当数据不存在的时候,再重新置初始值
if(IntervalDatabase.getInstance(MyApplication.getContext()).getInterval() == 0){
//置设置参数的初始值
IntervalDatabase.getInstance(MyApplication.getContext()).add(7);//更新频率
//初始相册
List<String> list = new ArrayList<>();
String[] albums = MyApplication.getContext().getResources().getStringArray(R.array.Albums);
for(String name : albums) {
list.add(name);
}
AlbumDatabase.getInstance(MyApplication.getContext()).add(list);
}
}
public static Context getContext() {
return mContext;
}
public static int getScreenWidth(){
return mScreenWidth;
}
public static int getScreenHeight(){
return mScreenHeight;
}
}
2.MainActivity
检查网络连接情况、相册是否从因特网爬取图片URL。若没有从因特网爬取图片URL,则进行爬取。检查数据库中的图片URL是否已经过期,若过期,则重新爬取图片URL。
public class MainActivity extends BaseActivity {
public static Handler mHandler;
private final static long UPDATE_UNIT = 24*60*60*1000; // 单位
private static final String TAG = "MainActivity";
// private final static long UPDATE_UNIT = 60*1000; // 单位-测试用
private int picture_num; //每个相册里面的照片数量
private LinearLayout lv_loading;
private LinearLayout lv_network;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler();
lv_network = (LinearLayout)findViewById(R.id.lv_network);
lv_loading = (LinearLayout) findViewById(R.id.lv_loading);
ImageView iv1 = (ImageView)findViewById(R.id.iv_guide_tip1);
ImageView iv2 = (ImageView)findViewById(R.id.iv_guide_tip2);
//缩放动画
ScaleAnimation sa1 = new ScaleAnimation(0.5f,1,0.5f,1,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f);
sa1.setDuration(1000);
sa1.setInterpolator(new BounceInterpolator());
iv1.startAnimation(sa1);
//缩放动画
ScaleAnimation sa2 = new ScaleAnimation(0.5f,1,0.5f,1,
Animation.RELATIVE_TO_SELF,0.5f,
Animation.RELATIVE_TO_SELF,0.5f);
sa2.setDuration(1000);
sa2.setInterpolator(new BounceInterpolator());
sa2.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if(!NetworkUtil.isNetworkAvailable(MyApplication.getContext())){
Log.d(TAG, "isNetworkAvailable FAIL");
lv_network.setVisibility(View.VISIBLE);
//avi_process.hide();
lv_loading.setVisibility(View.GONE);
} else {
Log.d(TAG, "isNetworkAvailable SUCCESS");
lv_network.setVisibility(View.GONE);
//avi_process.show();
lv_loading.setVisibility(View.VISIBLE);
if(checkIsNeedUpdate()){
GetPictureUrlService.startAction(mContext);
} else {
//不需要下载直接进入
startAction(HomeActivity.class);
finish();
}
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
iv2.startAnimation(sa2);
picture_num = getResources().getInteger(R.integer.page_num);
}
//检查数量是否正确,规则是每个相册最起码包含一个子照片
private boolean checkNum(){
boolean isTrue = true;
List<Album> list = UrlDatabase.getInstance(this).findAll(AlbumDatabase.getInstance(this).findAll());
for (Album album : list) {
if (album.getUrls().size() < 1) {
isTrue = false;
break;
}
}
return isTrue;
}
private boolean checkIsNeedUpdate(){
boolean flag=false;
long currentTimeMillis = UrlDatabase.getInstance(this).findCurrentTimeMillis();
if(currentTimeMillis == 0) {
flag = true;
} else {
//先判断下载的条数对不对,有可能后台返回的数目小于程序预置的数目
if(!checkNum()) {
flag = true;
}else {
//上次下载距离现在时间戳
long now = System.currentTimeMillis();
long times = now - UrlDatabase.getInstance(this).findCurrentTimeMillis();
int update_date = IntervalDatabase.getInstance(this).getInterval();//更新频率
if (times >= (update_date * UPDATE_UNIT)) {
flag = true;
}
}
}
return flag;
}
public void btnNetwork(View view) {
if(!NetworkUtil.isNetworkAvailable(MyApplication.getContext())){
Log.d(TAG, "isNetworkAvailable FAIL");
lv_network.setVisibility(View.VISIBLE);
//avi_process.hide();
lv_loading.setVisibility(View.GONE);
} else {
Log.d(TAG, "isNetworkAvailable SUCCESS");
lv_network.setVisibility(View.GONE);
//avi_process.show();
lv_loading.setVisibility(View.VISIBLE);
if(checkIsNeedUpdate()){
GetPictureUrlService.startAction(this);
} else {
//不需要下载直接进入
startAction(HomeActivity.class);
finish();
}
}
}
class MyHandler extends Handler{
@Override
public void handleMessage(final Message msg) {
//下载已完成
startAction(HomeActivity.class);
finish();
}
}
}
3.HomeActivity
分两部分,其一是固定部分(包括本地照片和设置);其二是从图片URL向网络获取bitmap,获取到的图片缓存到本地硬盘和缓存中(此处是借助郭大神的思想
Android照片墙完整版,完美结合LruCache和DiskLruCache ,大家可以去参考下)。
HomeActivity的启动模式是singleTask模式,因为在设置Activity中会增加一个相册,增加完后会自动回退到HomeActivity,且需要将HomeActivity上面的Activity全部出栈。所以在onResume()方法中增加了对设置相册是否有变化的判断。
public class HomeActivity extends BaseActivity {
private static final String YOUR_PICTURE = "您的照片";
private static final String SETTING = "设置";
ArrayList<ImageItem> images = null;
private List<String> list;
private List<HomeItem> mHomeItemList;
private HomeAdapter mAadapter;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == ImagePicker.RESULT_CODE_ITEMS) {
if (data != null && requestCode == 100) {
images = (ArrayList<ImageItem>) data.getSerializableExtra(ImagePicker.EXTRA_RESULT_ITEMS);
Log.d("HomeActivity", "" + images.get(0).path);
PictureBigActivity.startAction(this, true, images.get(0).path);
} else {
Toast.makeText(this, "没有数据", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
initData();
final RecyclerView recycler_home = (RecyclerView)findViewById(R.id.recycler_home);
recycler_home.setLayoutManager(new GridLayoutManager(this, 2));
mAadapter = new HomeAdapter(HomeActivity.this, recycler_home, mHomeItemList, new HomeAdapter.SelectedItemListener() {
@Override
public void onSelected(String name) {
if (name.equals(YOUR_PICTURE)){
Intent intent = new Intent(HomeActivity.this, ImageGridActivity.class);
intent.putExtra(ImageGridActivity.EXTRAS_IMAGES,images);
startActivityForResult(intent, 100);
} else if (name.equals(SETTING)){
SettingActivity.startAction(mContext);
} else {
AlbumActivity.startAction(HomeActivity.this, name);
}
}
});
recycler_home.setAdapter(mAadapter);
}
@Override
protected void onResume() {
super.onResume();
boolean flag = false;
//用于判断设置中是否增加了相册
for (String name : AlbumDatabase.getInstance(mContext).findAll()) {
Log.d("HomeActivity", name);
if (!list.contains(name)) {
//当set中不包含当前的数据的时候才会进行更新操作
flag = true;
list.add(name);
String url = UrlDatabase.getInstance(this).findUrlsByName(name).get(0);
HomeItem item = new HomeItem(name, url, HomeItem.TYPE_NORMAL, 0);
mHomeItemList.add(item);
Log.d("HomeActivity", "add name:" + name);
}
}
if (flag) {
Log.d("HomeActivity", "notifyDataSetChanged");
mAadapter.notifyDataSetChanged();
}
}
private void initData(){
list = AlbumDatabase.getInstance(this).findAll();
for (String name : list) {
Log.d("HomeActivity", "initData:" + name);
}
mHomeItemList = new ArrayList<>();
mHomeItemList.add(new HomeItem(YOUR_PICTURE, "", HomeItem.TYPE_HEAD, R.drawable.select_album));
mHomeItemList.add(new HomeItem(SETTING, "", HomeItem.TYPE_HEAD, R.drawable.setting));
//检查数据库是否存在数据
List<Album> mAlbumList = UrlDatabase.getInstance(this).findAll(list);
if (mAlbumList != null && mAlbumList.size() > 0) {
for (Album album : mAlbumList) {
HomeItem item = new HomeItem(album.getName(), album.getUrls().get(0), HomeItem.TYPE_NORMAL, 0);
mHomeItemList.add(item);
}
}
}
public static void startAction(Context context) {
Intent intent = new Intent(context, HomeActivity.class);
context.startActivity(intent);
}
}
4.AlbumActivity
AlbumActivity使用从HomeActivity中传过来的图片名称,去本地数据库查找之前爬取的图片Url列表,然后传给RecyclerView进行加载图片的操作。
public class AlbumActivity extends BaseActivity {
private int mPageNumbers;
private static final String NAME = "album_name";//相册名称
private AlbumAdapter mAadapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_album);
initData();
TextView tv_title = (TextView) findViewById(R.id.tv_title);
Intent intent = getIntent();
if(intent != null) {
String name = intent.getStringExtra(NAME);
Log.d("AlbumActivity", name+ ",num:" +mPageNumbers);
tv_title.setText(name.split("-")[0]);
List<String> list = UrlDatabase.getInstance(this).findUrlsByName(name);
final RecyclerView recycler_album = (RecyclerView) findViewById(R.id.recycler_album);
recycler_album.setLayoutManager(new GridLayoutManager(this, 2));
mAadapter = new AlbumAdapter(AlbumActivity.this, recycler_album,list, new AlbumAdapter.ClickListener() {
@Override
public void onClick(String url) {
PictureBigActivity.startAction(AlbumActivity.this, false, url);
}
});
recycler_album.setAdapter(mAadapter);
}
}
private void initData(){
mPageNumbers = getResources().getInteger(R.integer.page_num);
}
public static void startAction(Context context, String value) {
Intent intent = new Intent(context, AlbumActivity.class);
intent.putExtra("album_name",value);
context.startActivity(intent);
}
}
5.PictureBigActivity
显示大图,并设置块数。首先判断图片来源是什么?是本地相册选择,还是网络爬取的图片。然后取得该图片的Bitmap对象,并传递给自定义图片控件(该控件根据穿入图片块数计算每个小块的分割线的路径规则,并取得路径,绘制出来)。
效果如下所示。
public class PictureBigActivity extends BaseActivity {
private static final String TAG = "PictureBigActivity";
private PieceImageView iv_big_album;
private TextView tv_album_num;
private DiscreteSeekBar sb_num;
private ImageView iv_bg;
private String mUrl;
private boolean isFromAlbum;//是否来自于相册
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_album_big);
initViews();
Intent intent = getIntent();
if(intent != null) {
mUrl = intent.getStringExtra("url");
isFromAlbum = intent.getBooleanExtra("isFromAlbum", false);
sb_num.setOnProgressChangeListener(new OnProgressChangeListener());
tv_album_num.setText("块数:" + sb_num.getProgress()*sb_num.getProgress());
iv_bg.post(new Runnable() {
@Override
public void run() {
setImageView();
}
});
}
}
private void setImageView(){
if(isFromAlbum){
Bitmap bitmap = BitmapUtil.getBitmap(BitmapFactory.decodeFile(mUrl), iv_bg.getWidth(), iv_bg.getHeight());
iv_big_album.setImageBitmap(bitmap);
}else {
ImageDownloader loader = new ImageDownloader(this);
Bitmap bitmap = loader.getBitmapCache(mUrl);
if (bitmap != null) {
Log.d(TAG, "getBitmapCache OK");
iv_big_album.setImageBitmap(bitmap);
} else {
loader.loadImage(mUrl, iv_bg.getWidth(), iv_bg.getHeight(),
new ImageDownloader.AsyncImageLoaderListener() {
@Override
public void onImageLoader(Bitmap bitmap) {
Log.d(TAG, "loadImage OK");
iv_big_album.setImageBitmap(bitmap);
}
});
}
}
}
private void initViews() {
iv_big_album = (PieceImageView)findViewById(R.id.iv_big_album);
tv_album_num = (TextView) findViewById(R.id.tv_album_num);
sb_num = (DiscreteSeekBar)findViewById(R.id.sb_num);
iv_bg = (ImageView)findViewById(R.id.iv_bg);
}
/**
* 开始玩游戏
* @param view
*/
public void btnPlay(View view) {
finish();
GameActivity.startAction(this, isFromAlbum, mUrl, iv_big_album.getPieceSplit());
}
public static void startAction(Context context, boolean isFromAlbum, String value) {
Intent intent = new Intent(context, PictureBigActivity.class);
intent.putExtra("url",value);
intent.putExtra("isFromAlbum",isFromAlbum);
context.startActivity(intent);
}
class OnProgressChangeListener implements DiscreteSeekBar.OnProgressChangeListener {
@Override
public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
iv_big_album.setSpans(seekBar.getProgress()*seekBar.getProgress());
tv_album_num.setText("块数:" + seekBar.getProgress()*seekBar.getProgress());
}
@Override
public void onStartTrackingTouch(DiscreteSeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(DiscreteSeekBar seekBar) {
}
}
}
下面是自定义控件的代码。
public class PieceImageView extends android.support.v7.widget.AppCompatImageView {
private static final String TAG = "PieceImageView";
private int split_width;
private Paint mBorderPaint;
private Paint mBitmapPaint;
private Paint mSplitPaint;
private Context mContext;
private int border_width;
private int border_color;
private int corner_radius;
private int mSpans = 4;
private List<Path> mPathList;
private List<PieceSplit> mPieceSplitList;
public PieceImageView(Context context) {
this(context, null);
}
public PieceImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PieceImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
mPathList = new ArrayList<>();
mBitmapPaint = new Paint();
mBorderPaint = new Paint();
mSplitPaint = new Paint();
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PieceImageView);
border_width = (int)ta.getDimension(R.styleable.PieceImageView_p_border_width, 1);
border_color = ta.getColor(R.styleable.PieceImageView_p_border_color, mContext.getResources().getColor(R.color.gray_white));
corner_radius = (int)ta.getDimension(R.styleable.PieceImageView_p_corner_radius, 4);
ta.recycle();
split_width = border_width;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//防止同样块数的会重绘
if(mPathList.size() == 0) {
mPieceSplitList = PiecePathUtil.mathPath(getWidth(), getHeight(), mSpans);
genPath(mPieceSplitList);
} else {
if(mPathList.size() != mSpans) {
mPieceSplitList = PiecePathUtil.mathPath(getWidth(), getHeight(), mSpans);
genPath(mPieceSplitList);
}
}
Log.d(TAG, "onLayout");
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDraw(Canvas canvas) {
BitmapDrawable drawable = (BitmapDrawable)getDrawable();
if(drawable == null) {
Log.d("PieceImageView", "drawable == null");
return;
}
Bitmap source = drawable.getBitmap();
Bitmap squaredBitmap = BitmapUtil.getBitmap(source, getWidth(), getHeight());
Rect rect = new Rect(0, 0, getWidth(), getHeight());
RectF rectF = new RectF(rect);
BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
mBitmapPaint.setShader(shader);
mBitmapPaint.setAntiAlias(true);
//画图bitmap
canvas.drawRoundRect(rectF,corner_radius,corner_radius,mBitmapPaint);
//画外边框
mBorderPaint.setStrokeWidth(border_width);
mBorderPaint.setColor(border_color);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
canvas.drawRoundRect(rectF,corner_radius,corner_radius,mBorderPaint);
//画分割线
mSplitPaint.setColor(border_color);
mSplitPaint.setStyle(Paint.Style.STROKE);
mSplitPaint.setAntiAlias(true);
mSplitPaint.setStrokeWidth(split_width);
for(Path path:mPathList) {
canvas.drawPath(path, mSplitPaint);
}
// canvas.drawPath(mPathList.get(1),mSplitPaint);
Log.d(TAG,"onDraw");
}
public void setSpans(int spans) {
mSpans = spans;
if(mSpans < 49) {
split_width = border_width;
} else if(mSpans < 64){
split_width = border_width/2 == 0 ? 1 : border_width/2;
} else {
split_width = 1;
}
//重新计算路径
mPieceSplitList = PiecePathUtil.mathPath(getWidth(), getHeight(), mSpans);
genPath(mPieceSplitList);
Log.d(TAG, "setSpans");
invalidate();
}
public List<PieceSplit> getPieceSplit() {
return mPieceSplitList;
}
private void genPath(List<PieceSplit> list){
long begin = System.currentTimeMillis();
PiecePool.getInstance().reset();
mPathList.clear();
for (PieceSplit pieceSplit : list) {
mPathList.add(PiecePathUtil.getPath(pieceSplit));
}
Log.d("ms", "ms11:" + (System.currentTimeMillis() - begin));
}
}
6.GameActivity
首先还是取得图片的来源是哪并判断,然后取得Bitmap对象。其次,取得从PictureBigActivity中传过来的计算好的小图像块的路径规则列表。
将路径规则和Bitmap对象进行计算,得到小图像块,最终位置,随机移动到的位置,并保存在对象列表中。
在对象列表中分别取出对象,并创建自定义的移动自定义控件MoveImageView,同时为控件添加动画。动画完成后,则可以进行拼图游戏。
拼图完成后会有动画提示拼图成功。
public class GameActivity extends BaseActivity {
private static final String TAG = "GameActivity";
private Context mContext;
private String url;
private PieceSplitList pieceSplitList;
private ScaleFrameLayout sfl;
//private FrameLayout fl;
private PieceSplitImageView iv_game_main;
private RelativeLayout rl_parent;
private ImageView iv_end;
private TextView tv_success_tip;
private boolean isFromAlbum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
mContext = this;
iv_game_main = (PieceSplitImageView)findViewById(R.id.iv_game_main);
sfl = (ScaleFrameLayout)findViewById(R.id.sfl);
rl_parent = (RelativeLayout)findViewById(R.id.rl_parent);
iv_end = (ImageView) findViewById(R.id.iv_end);
tv_success_tip = (TextView)findViewById(R.id.tv_success_tip);
Intent intent = getIntent();
if (intent != null) {
url = intent.getStringExtra("url");
isFromAlbum = intent.getBooleanExtra("isFromAlbum", false);
pieceSplitList = (PieceSplitList)intent.getSerializableExtra("list");
iv_game_main.setList(pieceSplitList.getList());
sfl.post(new Runnable() {
@Override
public void run() {
setImageView();
}
});
MoveImageSet.VIEW_LIST = new HashMap<>();
MoveImageSet.VIEW_NUM = pieceSplitList.getList().size();
}
}
private void setImageView(){
if(isFromAlbum){
Bitmap bitmap = BitmapUtil.getBitmap(BitmapFactory.decodeFile(url), sfl.getWidth(), sfl.getHeight());
iv_game_main.setImageBitmap(bitmap);
handleBitmap(bitmap);
}else {
ImageDownloader loader = new ImageDownloader(this);
Bitmap bitmap = loader.getBitmapCache(url);
if (bitmap != null) {
Log.d(TAG, "getBitmapCache OK");
iv_game_main.setImageBitmap(bitmap);
handleBitmap(bitmap);
} else {
loader.loadImage(url, sfl.getWidth(), sfl.getHeight(),
new ImageDownloader.AsyncImageLoaderListener() {
@Override
public void onImageLoader(Bitmap bitmap) {
Log.d(TAG, "loadImage OK");
handleBitmap(bitmap);
}
});
}
}
}
private void handleBitmap(final Bitmap bitmap){
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "width:" + sfl.getWidth() + ",height:" + sfl.getHeight());
final Bitmap source = BitmapUtil.getBitmap(bitmap, sfl.getWidth(), sfl.getHeight());
float unit = (pieceSplitList.getList().get(0).getRight() - pieceSplitList.getList().get(0).getLeft()) / 3;
float unit_element = unit / 5;
float unit_width = unit * 2 / 3;
final List<ClipPieceBitmap> pieceBitmaps = BitmapUtil.clipBitmap(mContext, source,
pieceSplitList.getList(),
sfl.getX(),
sfl.getY(),
unit_element + unit_width);
runOnUiThread(new Runnable() {
@Override
public void run() {
for (ClipPieceBitmap clipPieceBitmap : pieceBitmaps) {
addView(clipPieceBitmap);
}
iv_game_main.setVisibility(View.GONE);
iv_end.setImageBitmap(source);
iv_end.setVisibility(View.GONE);
}
});
}
}).start();
}
private void addView(ClipPieceBitmap clipPieceBitmap){
MoveImageView view = new MoveImageView(mContext);
view.setImageBitmap(clipPieceBitmap.getBitmap());
view.setX(clipPieceBitmap.getX());
view.setY(clipPieceBitmap.getY());
view.setEndX(clipPieceBitmap.getX());
view.setEndY(clipPieceBitmap.getY());
view.setIndex(clipPieceBitmap.getIndex());
view.setListener(new MoveImageView.EndListener() {
@Override
public void onEnd() {
if (MoveImageSet.isAllEnd()){
//拼图成功
MoveImageSet.allInvisible();
iv_end.setVisibility(View.VISIBLE);
tv_success_tip.setVisibility(View.VISIBLE);
ObjectAnimator alpha = ObjectAnimator.ofFloat(tv_success_tip, "alpha", 1f, 0.5f,0.9f);
alpha.setDuration(3000);
alpha.start();
}
}
});
//设置动画效果
ObjectAnimator moveX = ObjectAnimator.ofFloat(view, "x", clipPieceBitmap.getX(),clipPieceBitmap.getAnimX());
ObjectAnimator moveY = ObjectAnimator.ofFloat(view, "y", clipPieceBitmap.getY(),clipPieceBitmap.getAnimY());
AnimatorSet set = new AnimatorSet();
set.playTogether(moveX,moveY);
set.setDuration(2000);
set.start();
rl_parent.addView(view);
//根据索引存储到List表中
MoveImageSet.VIEW_LIST.put(view.getIndex(), view);
}
public static void startAction(Context context, boolean isFromAlbum, String url, List<PieceSplit> list) {
Intent intent = new Intent(context, GameActivity.class);
intent.putExtra("url", url);
intent.putExtra("isFromAlbum",isFromAlbum);
intent.putExtra("list", new PieceSplitList(list));
context.startActivity(intent);
}
}
public class MoveImageView extends android.support.v7.widget.AppCompatImageView {
private static final String TAG = "MoveImageView";
private int index;
private boolean isEnd;//是否已经移动到指定位置
private float endX;
private float endY;
//当控件移动到指定位置附件时的误差
private int distance;
private int lastX = 0;
private int lastY = 0;
private EndListener listener;
public MoveImageView(Context context) {
this(context, null);
}
public MoveImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MoveImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
distance = DisplayUtil.dip2px(context,20);
isEnd = false;
}
public void setListener(EndListener listener) {
this.listener = listener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (isEnd){
//如果已经移动到指定位置,则不允许再次移动了
return true;
}
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
this.bringToFront();//当按下图片的时候显示在最上层
lastX = (int)event.getRawX();
lastY = (int)event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
handleMove(event);
break;
case MotionEvent.ACTION_UP:
handleUp();
break;
}
return true;
}
private void handleUp(){
//Log.d(TAG, "ACTION_UP");
//Log.d(TAG,"x:"+getX()+",y:"+getY() + ",endX:" + getEndX() + ",endY:"+getEndY()+",distance:"+distance);
if(Math.abs(getEndX()-getX()) <= distance && Math.abs(getEndY()-getY()) <= distance){
if (PieceLocUtil.isLeftTop(index)
|| PieceLocUtil.isRightTop(index, MoveImageSet.VIEW_NUM)
|| PieceLocUtil.isLeftBottom(index, MoveImageSet.VIEW_NUM)
|| PieceLocUtil.isRightBottom(index, MoveImageSet.VIEW_NUM)) {
//如果是四个角,可已直接放置图片
endMove();
} else if (PieceLocUtil.isLeft(index, MoveImageSet.VIEW_NUM)){
//如果是左边中间部分,则判断其上边或者下边或者右边是否已经放置好,放置好后从哪里进去
if(MoveImageSet.VIEW_LIST.get(PieceLocUtil.getTopIndex(index, MoveImageSet.VIEW_NUM)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getBottomIndex(index, MoveImageSet.VIEW_NUM)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getRightIndex(index)).isEnd()){
endMove();
}
} else if(PieceLocUtil.isRight(index, MoveImageSet.VIEW_NUM)){
//如果是右边中间部分,则判断其上边或者下边或者左边边是否已经放置好,放置好后从哪里进去
if(MoveImageSet.VIEW_LIST.get(PieceLocUtil.getTopIndex(index, MoveImageSet.VIEW_NUM)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getBottomIndex(index, MoveImageSet.VIEW_NUM)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getLeftIndex(index)).isEnd()){
endMove();
}
} else if (PieceLocUtil.isTop(index, MoveImageSet.VIEW_NUM)){
//上边中间部分,则判断其左边或者右边或者下边是否已经放置好
if(MoveImageSet.VIEW_LIST.get(PieceLocUtil.getLeftIndex(index)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getRightIndex(index)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getBottomIndex(index, MoveImageSet.VIEW_NUM)).isEnd()){
endMove();
}
} else if (PieceLocUtil.isBottom(index, MoveImageSet.VIEW_NUM)){
//下边中间部分,则判断其左边或者右边或者上边是否已经放置好
if(MoveImageSet.VIEW_LIST.get(PieceLocUtil.getLeftIndex(index)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getRightIndex(index)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getTopIndex(index, MoveImageSet.VIEW_NUM)).isEnd()){
endMove();
}
} else {
//中间部分,则根据上下左右是否有放置好的来判断是否可以放置
if(MoveImageSet.VIEW_LIST.get(PieceLocUtil.getLeftIndex(index)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getRightIndex(index)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getTopIndex(index, MoveImageSet.VIEW_NUM)).isEnd()
||MoveImageSet.VIEW_LIST.get(PieceLocUtil.getBottomIndex(index, MoveImageSet.VIEW_NUM)).isEnd()){
endMove();
}
}
}
}
private void endMove() {
setX(getEndX());
setY(getEndY());
SoundUtil.getInstance().playDing(R.raw.ding);
isEnd = true;
if(listener != null) {
listener.onEnd();
}
}
private void handleMove(MotionEvent event){
int movieX = (int)event.getRawX() - lastX;
int movieY = (int)event.getRawY() - lastY;
if(Math.abs(movieX) > 1 || Math.abs(movieY) > 1) {
int x = (int)getX() + movieX;
int y = (int)getY() + movieY;
// Log.d(TAG, "view left:"+getLeft()+",right:"+getRight()+",top:"+getTop()+",bottom:"+getBottom()+",x:"+getX()+",y:"+getY()+
// ",width:" + getWidth() + ",height:"+getHeight() + ",movieX:" + movieX + ",movieY:" + movieY);
if(x < 0) {
x = 0;
} else if (x > MyApplication.getScreenWidth() - getWidth()){
x = MyApplication.getScreenWidth() - getWidth();
}
if(y < 0){
y = 0;
} else if(y > MyApplication.getScreenHeight() - getHeight()){
y = MyApplication.getScreenHeight() - getHeight();
}
setX(x);
setY(y);
}
lastX = (int)event.getRawX();
lastY = (int)event.getRawY();
}
public float getEndX() {
return endX;
}
public float getEndY() {
return endY;
}
public int getIndex() {
return index;
}
public boolean isEnd() {
return isEnd;
}
public void setEndX(float endX) {
this.endX = endX;
}
public void setEndY(float endY) {
this.endY = endY;
}
public void setIndex(int index) {
this.index = index;
}
public interface EndListener {
public void onEnd();
}
}