您的位置:首页 > 移动开发 > Android开发

Android 高仿 频道管理----网易、今日头条、腾讯视频 (可以拖动的GridView)附源码DEMO

2016-05-27 10:04 746 查看
http://blog.csdn.net/vipzjyno1/article/details/25005851

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[-]


一开发心里历程
二开发前的准备
三开发思路
四流程图

五核心代码
六源码下载

距离上次发布(android高仿系列)今日头条
--新闻阅读器 (二)

相关的内容已经半个月了,最近利用空闲时间,把今日头条客户端完善了下。完善的功能一个一个全部实现后,就放整个源码。开发的进度就是按照一个一个功能的思路走的,所以开发一个小的功能,如果有用,就写一个专门的博客以便有人用到独立的功能可以方便使用。

这次实现的功能是很多新闻阅读器(网易,今日头条,360新闻等)以及腾讯视频等里面都会出现的频道管理功能。

下面先上这次实现功能的效果图:(注:这个效果图没有拖拽的时候移动动画,DEMO里面有,可以下载看看)





一、开发心里历程

刚开始接触这个的时候,不知道要如何实现,去网上翻了一大堆资料,懂了个大概,就是目前可以找到的都是拖拽的时候,不带移动动画的,和线上的客户端交互效果相差甚远,在反反复复的尝试查看相关东西,大致的做了出来,目前在模拟器上似乎有一点小BUG,真机测试没有问题,就先放上来,如果发现问题在修改优化。代码反面,没有好好的修改调整,可能会有点乱,请见谅哈。

二、开发前的准备

1.了解重写View的相关知识,并且知道GridView的一些内部方法,如:怎么通过触摸的坐标获取对应的position等(这里我采用的是继承GridView控件)

2.了解屏幕触摸动作传递原理 这里我以前转载的一篇或许会有帮助:Android事件分发机制完全解析,带你从源码的角度彻底理解(全)

3.了解位移动画Animation,本DEMO中主要用到:TranslateAnimation 平移动画

4.了解WindowManager的窗口机制,这里的item拖拽等都要设计到这个。

5.了解SQLiteDatabase 以及SQLiteOpenHelper等数据库操作相关的类,本DEMO中主要用到数据库进行存储频道信息,如果你要用文档进行存储读取也可以。

三、开发思路

1. 获取数据库中频道的列表,如果为空,赋予默认列表,并存入数据库,之后通过对应的适配器赋给对应的GridView

2. 2个GridView--(1.DragGrid 2. OtherGridView)

DragGrid 用于显示我的频道,带有长按拖拽效果

OtherGridView用于显示更多频道,不带推拽效果

注:由于屏幕大小不一定,外层使用ScrollView,所以2者都要重写计算高度

3. 点击2个GridView的时候,根据点击的Item对应的position,获取position对应的view,进行创建一层移动的动画层

起始位置:点击的positiongetLocationInWindow()获取。终点位置:另一个GridView的最后个ITEM 的position + 1的位置。

并赋予移动动画,等动画结束后对2者对应的频道列表进行数据的remove和add操作。

4. 设置点击和拖动的限制条件,如 推荐 这个ITEM是不允许用户操作的。

5. 拖动的DragGrid的操作:

(1)长按获取长按的ITEM的position -- dragPosition 以及对应的view ,手指触摸屏幕的时候,调用onInterceptTouchEvent来获取MotionEvent.ACTION_DOWN事件,获取对应的数据。由于这里是继承了GridView,所以长按时间可以通过setOnItemLongClickListener监听来执行,或则你也可以通过计算点击时间来监听是否长按。

(2)通过onTouchEvent(MotionEvent ev)来监听手指的移动和抬起动作。当它移动到 其它的item下面,并且下方的item对应的position 不等于 dragPosition,进行数据交换,并且2者之间的所有item进行移动动画,动画结束后,数据更替刷新界面。

(3) 抬起手后,清除掉拖动时候创建的view,让GridView中的数据显示。

6. 退出时候,将改变后的频道列表存入数据库。

四、流程图

下面是大体的流程图:





五、核心代码

点击进行添加删除:

[java] view
plain copy







/** GRIDVIEW对应的ITEM点击监听接口 */

@Override

public void onItemClick(AdapterView<?> parent, final View view, final int position,long id) {

//如果点击的时候,之前动画还没结束,那么就让点击事件无效

if(isMove){

return;

}

switch (parent.getId()) {

case R.id.userGridView:

//position为 0,1 的不可以进行任何操作

if (position != 0 && position != 1) {

final ImageView moveImageView = getView(view);

if (moveImageView != null) {

TextView newTextView = (TextView) view.findViewById(R.id.text_item);

final int[] startLocation = new int[2];

newTextView.getLocationInWindow(startLocation);

final ChannelItem channel = ((DragAdapter) parent.getAdapter()).getItem(position);//获取点击的频道内容

otherAdapter.setVisible(false);

//添加到最后一个

otherAdapter.addItem(channel);

new Handler().postDelayed(new Runnable() {

public void run() {

try {

int[] endLocation = new int[2];

//获取终点的坐标

otherGridView.getChildAt(otherGridView.getLastVisiblePosition()).getLocationInWindow(endLocation);

MoveAnim(moveImageView, startLocation , endLocation, channel,userGridView);

userAdapter.setRemove(position);

} catch (Exception localException) {

}

}

}, 50L);

}

}

break;

case R.id.otherGridView:

final ImageView moveImageView = getView(view);

if (moveImageView != null){

TextView newTextView = (TextView) view.findViewById(R.id.text_item);

final int[] startLocation = new int[2];

newTextView.getLocationInWindow(startLocation);

final ChannelItem channel = ((OtherAdapter) parent.getAdapter()).getItem(position);

userAdapter.setVisible(false);

//添加到最后一个

userAdapter.addItem(channel);

new Handler().postDelayed(new Runnable() {

public void run() {

try {

int[] endLocation = new int[2];

//获取终点的坐标

userGridView.getChildAt(userGridView.getLastVisiblePosition()).getLocationInWindow(endLocation);

MoveAnim(moveImageView, startLocation , endLocation, channel,otherGridView);

otherAdapter.setRemove(position);

} catch (Exception localException) {

}

}

}, 50L);

}

break;

default:

break;

}

}

移动动画:

[java] view
plain copy







<span style="font-size:14px;">private void MoveAnim(View moveView, int[] startLocation,int[] endLocation, final ChannelItem moveChannel,

final GridView clickGridView) {

int[] initLocation = new int[2];

//获取传递过来的VIEW的坐标

moveView.getLocationInWindow(initLocation);

//得到要移动的VIEW,并放入对应的容器中

final ViewGroup moveViewGroup = getMoveViewGroup();

final View mMoveView = getMoveView(moveViewGroup, moveView, initLocation);

//创建移动动画

TranslateAnimation moveAnimation = new TranslateAnimation(

startLocation[0], endLocation[0], startLocation[1],

endLocation[1]);

moveAnimation.setDuration(300L);//动画时间

//动画配置

AnimationSet moveAnimationSet = new AnimationSet(true);

moveAnimationSet.setFillAfter(false);//动画效果执行完毕后,View对象不保留在终止的位置

moveAnimationSet.addAnimation(moveAnimation);

mMoveView.startAnimation(moveAnimationSet);

moveAnimationSet.setAnimationListener(new AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {

isMove = true;

}

@Override

public void onAnimationRepeat(Animation animation) {

}

@Override

public void onAnimationEnd(Animation animation) {

moveViewGroup.removeView(mMoveView);

// instanceof 方法判断2边实例是不是一样,判断点击的是DragGrid还是OtherGridView

if (clickGridView instanceof DragGrid) {

otherAdapter.setVisible(true);

otherAdapter.notifyDataSetChanged();

userAdapter.remove();

}else{

userAdapter.setVisible(true);

userAdapter.notifyDataSetChanged();

otherAdapter.remove();

}

isMove = false;

}

});

}</span>

可拖拽的DragGrid代码:

[java] view
plain copy







public class DragGrid extends GridView {

/** 点击时候的X位置 */

public int downX;

/** 点击时候的Y位置 */

public int downY;

/** 点击时候对应整个界面的X位置 */

public int windowX;

/** 点击时候对应整个界面的Y位置 */

public int windowY;

/** 屏幕上的X */

private int win_view_x;

/** 屏幕上的Y */

private int win_view_y;

/** 拖动的里x的距离 */

int dragOffsetX;

/** 拖动的里Y的距离 */

int dragOffsetY;

/** 长按时候对应postion */

public int dragPosition;

/** Up后对应的ITEM的Position */

private int dropPosition;

/** 开始拖动的ITEM的Position */

private int startPosition;

/** item高 */

private int itemHeight;

/** item宽 */

private int itemWidth;

/** 拖动的时候对应ITEM的VIEW */

private View dragImageView = null;

/** 长按的时候ITEM的VIEW */

private ViewGroup dragItemView = null;

/** WindowManager管理器 */

private WindowManager windowManager = null;

/** */

private WindowManager.LayoutParams windowParams = null;

/** item总量 */

private int itemTotalCount;

/** 一行的ITEM数量 */

private int nColumns = 4;

/** 行数 */

private int nRows;

/** 剩余部分 */

private int Remainder;

/** 是否在移动 */

private boolean isMoving = false;

/** */

private int holdPosition;

/** 拖动的时候放大的倍数 */

private double dragScale = 1.2D;

/** 震动器 */

private Vibrator mVibrator;

/** 每个ITEM之间的水平间距 */

private int mHorizontalSpacing = 15;

/** 每个ITEM之间的竖直间距 */

private int mVerticalSpacing = 15;

/* 移动时候最后个动画的ID */

private String LastAnimationID;

public DragGrid(Context context) {

super(context);

init(context);

}

public DragGrid(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

init(context);

}

public DragGrid(Context context, AttributeSet attrs) {

super(context, attrs);

init(context);

}

public void init(Context context) {

mVibrator = (Vibrator) context

.getSystemService(Context.VIBRATOR_SERVICE);

// 将布局文件中设置的间距dip转为px

mHorizontalSpacing = DataTools.dip2px(context, mHorizontalSpacing);

}

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

// TODO Auto-generated method stub

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

downX = (int) ev.getX();

downY = (int) ev.getY();

windowX = (int) ev.getX();

windowY = (int) ev.getY();

setOnItemClickListener(ev);

}

return super.onInterceptTouchEvent(ev);

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

// TODO Auto-generated method stub

boolean bool = true;

if (dragImageView != null

&& dragPosition != AdapterView.INVALID_POSITION) {

// 移动时候的对应x,y位置

bool = super.onTouchEvent(ev);

int x = (int) ev.getX();

int y = (int) ev.getY();

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

downX = (int) ev.getX();

windowX = (int) ev.getX();

downY = (int) ev.getY();

windowY = (int) ev.getY();

break;

case MotionEvent.ACTION_MOVE:

onDrag(x, y, (int) ev.getRawX(), (int) ev.getRawY());

if (!isMoving) {

OnMove(x, y);

}

if (pointToPosition(x, y) != AdapterView.INVALID_POSITION) {

break;

}

break;

case MotionEvent.ACTION_UP:

stopDrag();

onDrop(x, y);

requestDisallowInterceptTouchEvent(false);

break;

default:

break;

}

}

return super.onTouchEvent(ev);

}

/** 在拖动的情况 */

private void onDrag(int x, int y, int rawx, int rawy) {

if (dragImageView != null) {

windowParams.alpha = 0.6f;

windowParams.x = rawx - win_view_x;

windowParams.y = rawy - win_view_y;

windowManager.updateViewLayout(dragImageView, windowParams);

}

}

/** 在松手下放的情况 */

private void onDrop(int x, int y) {

// 根据拖动到的x,y坐标获取拖动位置下方的ITEM对应的POSTION

int tempPostion = pointToPosition(x, y);

dropPosition = tempPostion;

DragAdapter mDragAdapter = (DragAdapter) getAdapter();

// 显示刚拖动的ITEM

mDragAdapter.setShowDropItem(true);

// 刷新适配器,让对应的ITEM显示

mDragAdapter.notifyDataSetChanged();

}

/**

* 长按点击监听

* @param ev

*/

public void setOnItemClickListener(final MotionEvent ev) {

setOnItemLongClickListener(new OnItemLongClickListener() {

@Override

public boolean onItemLongClick(AdapterView<?> parent, View view,

int position, long id) {

int x = (int) ev.getX();// 长安事件的X位置

int y = (int) ev.getY();// 长安事件的y位置

startPosition = position;// 第一次点击的postion

dragPosition = position;

if (startPosition <= 1) {

return false;

}

ViewGroup dragViewGroup = (ViewGroup) getChildAt(dragPosition

- getFirstVisiblePosition());

TextView dragTextView = (TextView) dragViewGroup

.findViewById(R.id.text_item);

dragTextView.setSelected(true);

dragTextView.setEnabled(false);

itemHeight = dragViewGroup.getHeight();

itemWidth = dragViewGroup.getWidth();

itemTotalCount = DragGrid.this.getCount();

int row = itemTotalCount / nColumns;// 算出行数

Remainder = (itemTotalCount % nColumns);// 算出最后一行多余的数量

if (Remainder != 0) {

nRows = row + 1;

} else {

nRows = row;

}

// 如果特殊的这个不等于拖动的那个,并且不等于-1

if (dragPosition != AdapterView.INVALID_POSITION) {

// 释放的资源使用的绘图缓存。如果你调用buildDrawingCache()手动没有调用setDrawingCacheEnabled(真正的),你应该清理缓存使用这种方法。

win_view_x = windowX - dragViewGroup.getLeft();// VIEW相对自己的X,半斤

win_view_y = windowY - dragViewGroup.getTop();// VIEW相对自己的y,半斤

dragOffsetX = (int) (ev.getRawX() - x);// 手指在屏幕的上X位置-手指在控件中的位置就是距离最左边的距离

dragOffsetY = (int) (ev.getRawY() - y);// 手指在屏幕的上y位置-手指在控件中的位置就是距离最上边的距离

dragItemView = dragViewGroup;

dragViewGroup.destroyDrawingCache();

dragViewGroup.setDrawingCacheEnabled(true);

Bitmap dragBitmap = Bitmap.createBitmap(dragViewGroup

.getDrawingCache());

mVibrator.vibrate(50);// 设置震动时间

startDrag(dragBitmap, (int) ev.getRawX(),

(int) ev.getRawY());

hideDropItem();

dragViewGroup.setVisibility(View.INVISIBLE);

isMoving = false;

requestDisallowInterceptTouchEvent(true);

return true;

}

return false;

}

});

}

public void startDrag(Bitmap dragBitmap, int x, int y) {

stopDrag();

windowParams = new WindowManager.LayoutParams();// 获取WINDOW界面的

// Gravity.TOP|Gravity.LEFT;这个必须加

windowParams.gravity = Gravity.TOP | Gravity.LEFT;

// 得到preview左上角相对于屏幕的坐标

windowParams.x = x - win_view_x;

windowParams.y = y - win_view_y;

// 设置拖拽item的宽和高

windowParams.width = (int) (dragScale * dragBitmap.getWidth());// 放大dragScale倍,可以设置拖动后的倍数

windowParams.height = (int) (dragScale * dragBitmap.getHeight());// 放大dragScale倍,可以设置拖动后的倍数

this.windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE

| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON

| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;

this.windowParams.format = PixelFormat.TRANSLUCENT;

this.windowParams.windowAnimations = 0;

ImageView iv = new ImageView(getContext());

iv.setImageBitmap(dragBitmap);

windowManager = (WindowManager) getContext().getSystemService(

Context.WINDOW_SERVICE);// "window"

windowManager.addView(iv, windowParams);

dragImageView = iv;

}

/** 停止拖动 ,释放并初始化 */

private void stopDrag() {

if (dragImageView != null) {

windowManager.removeView(dragImageView);

dragImageView = null;

}

}

/** 在ScrollView内,所以要进行计算高度 */

@Override

public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,

MeasureSpec.AT_MOST);

super.onMeasure(widthMeasureSpec, expandSpec);

}

/** 隐藏 放下 的ITEM */

private void hideDropItem() {

((DragAdapter) getAdapter()).setShowDropItem(false);

}

/** 获取移动动画 */

public Animation getMoveAnimation(float toXValue, float toYValue) {

TranslateAnimation mTranslateAnimation = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0F, Animation.RELATIVE_TO_SELF,

toXValue, Animation.RELATIVE_TO_SELF, 0.0F,

Animation.RELATIVE_TO_SELF, toYValue);// 当前位置移动到指定位置

mTranslateAnimation.setFillAfter(true);// 设置一个动画效果执行完毕后,View对象保留在终止的位置。

mTranslateAnimation.setDuration(300L);

return mTranslateAnimation;

}

/** 移动的时候触发 */

public void OnMove(int x, int y) {

// 拖动的VIEW下方的POSTION

int dPosition = pointToPosition(x, y);

// 判断下方的POSTION是否是最开始2个不能拖动的

if (dPosition > 1) {

if ((dPosition == -1) || (dPosition == dragPosition)) {

return;

}

dropPosition = dPosition;

if (dragPosition != startPosition) {

dragPosition = startPosition;

}

int movecount;

// 拖动的=开始拖的,并且 拖动的 不等于放下的

if ((dragPosition == startPosition)

|| (dragPosition != dropPosition)) {

// 移需要移动的动ITEM数量

movecount = dropPosition - dragPosition;

} else {

// 移需要移动的动ITEM数量为0

movecount = 0;

}

if (movecount == 0) {

return;

}

int movecount_abs = Math.abs(movecount);

if (dPosition != dragPosition) {

// dragGroup设置为不可见

ViewGroup dragGroup = (ViewGroup) getChildAt(dragPosition);

dragGroup.setVisibility(View.INVISIBLE);

float to_x = 1;// 当前下方positon

float to_y;// 当前下方右边positon

// x_vlaue移动的距离百分比(相对于自己长度的百分比)

float x_vlaue = ((float) mHorizontalSpacing / (float) itemWidth) + 1.0f;

// y_vlaue移动的距离百分比(相对于自己宽度的百分比)

float y_vlaue = ((float) mVerticalSpacing / (float) itemHeight) + 1.0f;

Log.d("x_vlaue", "x_vlaue = " + x_vlaue);

for (int i = 0; i < movecount_abs; i++) {

to_x = x_vlaue;

to_y = y_vlaue;

// 像左

if (movecount > 0) {

// 判断是不是同一行的

holdPosition = dragPosition + i + 1;

if (dragPosition / nColumns == holdPosition / nColumns) {

to_x = -x_vlaue;

to_y = 0;

} else if (holdPosition % 4 == 0) {

to_x = 3 * x_vlaue;

to_y = -y_vlaue;

} else {

to_x = -x_vlaue;

to_y = 0;

}

} else {

// 向右,下移到上,右移到左

holdPosition = dragPosition - i - 1;

if (dragPosition / nColumns == holdPosition / nColumns) {

to_x = x_vlaue;

to_y = 0;

} else if ((holdPosition + 1) % 4 == 0) {

to_x = -3 * x_vlaue;

to_y = y_vlaue;

} else {

to_x = x_vlaue;

to_y = 0;

}

}

ViewGroup moveViewGroup = (ViewGroup) getChildAt(holdPosition);

Animation moveAnimation = getMoveAnimation(to_x, to_y);

moveViewGroup.startAnimation(moveAnimation);

// 如果是最后一个移动的,那么设置他的最后个动画ID为LastAnimationID

if (holdPosition == dropPosition) {

LastAnimationID = moveAnimation.toString();

}

moveAnimation.setAnimationListener(new AnimationListener() {

@Override

public void onAnimationStart(Animation animation) {

// TODO Auto-generated method stub

isMoving = true;

}

@Override

public void onAnimationRepeat(Animation animation) {

// TODO Auto-generated method stub

}

@Override

public void onAnimationEnd(Animation animation) {

// TODO Auto-generated method stub

// 如果为最后个动画结束,那执行下面的方法

if (animation.toString().equalsIgnoreCase(

LastAnimationID)) {

DragAdapter mDragAdapter = (DragAdapter) getAdapter();

mDragAdapter.exchange(startPosition,

dropPosition);

startPosition = dropPosition;

dragPosition = dropPosition;

isMoving = false;

}

}

});

}

}

}

}

}

数据库SQLHelper文件

[java] view
plain copy







public class SQLHelper extends SQLiteOpenHelper {

public static final String DB_NAME = "database.db";// 数据库名称

public static final int VERSION = 1;

public static final String TABLE_CHANNEL = "channel";//数据表

public static final String ID = "id";//

public static final String NAME = "name";

public static final String ORDERID = "orderId";

public static final String SELECTED = "selected";

private Context context;

public SQLHelper(Context context) {

super(context, DB_NAME, null, VERSION);

this.context = context;

}

public Context getContext(){

return context;

}

@Override

public void onCreate(SQLiteDatabase db) {

// TODO 创建数据库后,对数据库的操作

String sql = "create table if not exists "+TABLE_CHANNEL +

"(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +

ID + " INTEGER , " +

NAME + " TEXT , " +

ORDERID + " INTEGER , " +

SELECTED + " SELECTED)";

db.execSQL(sql);

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

// TODO 更改数据库版本的操作

onCreate(db);

}

}

注:本DEMO中,加入了长按震动,所以在权限里面记得加上“

[java] view
plain copy







<!-- 在SDCard中创建与删除文件权限 -->

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<!-- 往SDCard写入数据权限 -->

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- 震动权限 -->

<uses-permission android:name="android.permission.VIBRATE"/>

六、源码下载

源码DEMO下载地址如下:下载地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: