山寨风,高仿大街app拖动删除或收藏效果来袭!

嗯,首先自我介绍一下,本人目前在校学生,安卓开发小菜一个,这是我的第一篇博客,希望能以这篇博客为起点,纪录自己的成长,也与各位安卓新手共勉。

进入正题,秉着无图无jb的观点,先上个效果图给各位看官看下,有兴趣的继续看,没兴趣的请便,o(^▽^)o。

总体模仿的还是挺像的,个人认为。

主要功能分析

1.对布局的旋转、透明度等的处理

2.自定义各种形状的图片(例如圆形图片)

3.图片的高斯模糊

4.动画处理


界面分析

其实刚开始想模仿这个界面的时候,我考虑的是ViewPager,不过他这个界面的功能是,不管你右滑还是左滑,这条item都会删掉,想想ViewPager好像有点麻烦,所以就放弃咯。
废话不多说,我直接说我的实现方法,主界面是一个FrameLayout,然后通过代码动态addView和removeView添加和删除item,当左滑或右滑到超过一定范围,便删除FrameLayout的最后一个item,并且重新添加一个到FrameLayout的第一个位置。根据手指在屏幕移动的距离来控制透明度以及旋转的变化。
不知道你们有没有看到,背景还有类似几张纸叠在一起的效果,这个是我自己P的背景图,不是用代码实现的。如果没看到,在文末自行下载运行看看。好了,说了这么多,接下来说下代码的讲解。

功能实现

主界面布局activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/rlRoot"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context="com.chenantoa.main.MainActivity">

    <FrameLayout
        android:id="@+id/flContainer"
        android:layout_width="match_parent"
        android:paddingTop="10dp"
        android:paddingBottom="20dp"
        android:layout_height="match_parent"
        android:layout_centerInParent="true">
        <ImageView
            android:layout_gravity="center_horizontal"
            android:src="@drawable/bg"
            android:layout_width="350dp"
            android:layout_height="480dp"/>
    </FrameLayout>
</RelativeLayout>
 
 


很简单,没什么好说的,一个FrameLayout,这是一个布局,用来陈放item的,里面的ImageView是个什么东西呢?是一个背景图片,也就是刚才上面所说的类似几张纸叠起来的效果,有人可能会问为什么不直接在FrameLayout写个backgroud呢?我一开始也是这么做的,那么我必须把FrameLayout的大小定义成容器的大小,也就是不能写成match_parent了,这里就出问题了。当容器里面的item旋转的时候,超过容器的那部分将不会显示。

不过我自己实验过,如果一开始就在FrameLayout声明好item,而不是动态添加的话,就算移动出了容器的范围,也能够显示,不知道为什么,有知道的大神可以在下面留言,谢谢。

item布局 stack_item.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    android:id="@+id/llItem"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="350dp"
    android:layout_height="500dp">

    <LinearLayout
        android:layout_width="350dp"
        android:layout_height="500dp"
        android:background="@drawable/item_bg"
        android:gravity="center_horizontal"
        android:orientation="vertical">

        <FrameLayout
            android:id="@+id/flAvater"
            android:layout_width="match_parent"
            android:layout_height="140dp">

            <com.chenantao.view.widget.MultipleShapeImg
                android:id="@+id/blurAvatar"
                android:layout_width="match_parent"
                android:layout_height="130dp"
                android:src="@mipmap/avatar"
                app:type="arcRectangle"/>

            <com.chenantao.view.widget.MultipleShapeImg
                android:id="@+id/roundAvatar"
                android:layout_width="75dp"
                android:layout_height="75dp"
                android:layout_gravity="center|bottom"
                android:src="@mipmap/avatar"
                app:type="round"/>

            <TextView
                android:id="@+id/tvUsername"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="10dp"
                android:textColor="@android:color/white"
                android:textSize="20sp"/>
        </FrameLayout>


        <TextView
            android:id="@+id/tvSchool"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:text="some message"
            android:textColor="@android:color/black"
            android:textSize="15sp"/>

        <TextView
            android:id="@+id/tvMajor"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:text="some message"
            android:textSize="15sp"/>

        <View
            android:layout_width="300dp"
            android:layout_height="1px"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="25dp"
            android:background="@android:color/darker_gray"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:gravity="center"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tvEntranceTime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:text="2013级入学"
                android:textSize="15sp"/>

            <View
                android:layout_width="1dp"
                android:layout_height="23dp"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="40dp"
                android:layout_marginTop="3dp"
                android:background="@android:color/darker_gray"/>

            <TextView
                android:id="@+id/tvAdress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:text="广州"
                android:textSize="15sp"/>
        </LinearLayout>

        <View
            android:layout_width="300dp"
            android:layout_height="1px"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="25dp"
            android:background="@android:color/darker_gray"/>

        <TextView
            android:id="@+id/tvSkill"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:text="some message"
            android:textSize="15sp"/>
    </LinearLayout>

    <ImageView
        android:id="@+id/ivIgnore"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:src="@mipmap/ignore"
        android:visibility="gone"/>

    <ImageView
        android:id="@+id/ivInterested"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:src="@mipmap/interested"
        android:visibility="gone"/>
</FrameLayout>

嗯,这个由点小长,不过都很简单,相信有点android基础的都能分析出item的布局。解释一下第一个FrameLayout里面的两个MultipleShapeImg是什么鬼,这是一个自定义ImageView,目的是实现圆形的图片以及那个下边是一条曲线的矩形图片,这个后边会说。接下来开始看java代码。

主界面Activity MainActivity.java

private FrameLayout mContainer;//framelayout容器
	private float mRotateFactor;//控制item旋转范围
	private double mItemAlphaFactor;//控制item透明度变化范围
	private double mItemIvAlphaFactor;//控制item上面的图片的透明度变化范围
	private float mLimitTranslateX = 100;//限制移动距离,当超过这个距离的时候,删除该item
	private List<User> mDatas;//item的数据
	private int mIndex = -1;//标识当前读取到数据的第几个下标

先定义一些变量,现在看不懂耶没关系,后面实现的时候就懂了。 在onCreate的时候对他们进行初始化。并且利用getDatas方法生成一些伪数据。这个比较简单,就不贴代码了,主要就是定义一个bean User类,然后进行一些伪数据赋值。
@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mDatas = GenerateData.getDatas();
		//从屏幕的最右边滑动到最左边,最多旋转60度
		int screenWidth = ScreenUtils.getScreenWidth(this);
		mRotateFactor = 60 * 1.0f / screenWidth;
		//左滑,透明度最少到0.3f
		mItemAlphaFactor = 0.7 * 1.0f / screenWidth / 2;
		//item上面图标透明度的变化,滑动4分之一屏幕的距离便使其完全显示
		mItemIvAlphaFactor = 4.0f / screenWidth;
		mContainer = (FrameLayout) findViewById(R.id.flContainer);
	}



定义个方法,用于添加view
public void addViewToBehind()
	{
		if (mIndex == mDatas.size() - 1)
		{
			return;
		}
		//加载布局
		final View item = LayoutInflater.from(this).inflate(R.layout.stack_item, null);
		FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(dp2px(350), dp2px(470), Gravity
				.CENTER_HORIZONTAL);
		item.setLayoutParams(lp);
		mContainer.addView(item, 1);
		//初始化item的数据
		User user = mDatas.get(++mIndex);//取出一条数据,并且增加index的下标
		ImageView roundAvatar = (ImageView) item.findViewById(R.id.roundAvatar);
		ImageView blurAvatar = (ImageView) item.findViewById(R.id.blurAvatar);
//		Log.e("cat", "avatar resId:" + user.getAvater());
		CatImageLoader.getInstance().loadImage(user.getAvater(), blurAvatar);
		CatImageLoader.getInstance().loadImage(user.getAvater(), roundAvatar);
		TextView tvUsername = (TextView) item.findViewById(R.id.tvUsername);
		TextView tvSchool = (TextView) item.findViewById(R.id.tvSchool);
		TextView tvMajor = (TextView) item.findViewById(R.id.tvMajor);
		TextView tvEntranceTime = (TextView) item.findViewById(R.id.tvEntranceTime);
		TextView tvSkill = (TextView) item.findViewById(R.id.tvSkill);
		final ImageView ivIgnore = (ImageView) item.findViewById(R.id.ivIgnore);
		final ImageView ivInterested = (ImageView) item.findViewById(R.id.ivInterested);
		tvUsername.setText(user.getName());
		tvSchool.setText(user.getSchool());
		tvMajor.setText(user.getMajor() + " | " + user.getSchoolLevel());
		tvEntranceTime.setText(user.getEntranceTime());
		tvSkill.setText("装逼 吹牛逼");
		//设置item的重心,主要是旋转的中心
		item.setPivotX(item.getLayoutParams().width / 2);
		item.setPivotY(item.getLayoutParams().height * 2);
		item.setOnTouchListener(new View.OnTouchListener()
		{
			float touchX, distanceX;

			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				switch (event.getAction())
				{
					case MotionEvent.ACTION_DOWN:
						touchX = event.getRawX();
						break;
					case MotionEvent.ACTION_MOVE:
						distanceX = event.getRawX() - touchX;
						item.setRotation(distanceX * mRotateFactor);
						//alpha scale 1~0.3
						//item的透明度为从1到0.3
						item.setAlpha(1 - (float) Math.abs(mItemAlphaFactor * distanceX));
						if (distanceX < 0)//如果为左滑
						{
							//显示忽略图标,隐藏感兴趣图标
							ivIgnore.setVisibility(View.VISIBLE);
							ivInterested.setVisibility(View.GONE);
							ivIgnore.setAlpha((float) (Math.abs(distanceX) * mItemIvAlphaFactor));
						} else
						{
							//显示感兴趣图标,隐藏忽略图标
							ivIgnore.setVisibility(View.GONE);
							ivInterested.setVisibility(View.VISIBLE);
							ivInterested.setAlpha((float) (distanceX * mItemIvAlphaFactor));
						}
						break;
					case MotionEvent.ACTION_UP:
						if (Math.abs(distanceX) > mLimitTranslateX)
						{
							View removeView = mContainer.getChildAt(mContainer.getChildCount() -
									1);
							removeView(removeView, distanceX < 0 ? true : false);
							addViewToBehind();
						} else
						{
							//复位
							item.setRotation(0);
							item.setAlpha(1);
							ivIgnore.setAlpha(1.0f);
							ivInterested.setAlpha(1.0f);
							ivIgnore.setVisibility(View.GONE);
							ivInterested.setVisibility(View.GONE);

						}
						break;
				}
				return true;
			}
		});
	}

嗯,代码挺长,不过不用紧张,都是一些简单的代码,注释已经写的挺清楚了,我简单解释一下。 注意这里,由于我们的FrameLayout方法里面有个ImageView作为背景图片,所以我们添加图片的时候,默认要从FrameLayout的下标为1的位置开始添加。
注意这里,由于我们的FrameLayout方法里面有个ImageView作为背景图片,所以我们添加图片的时候,默认要从FrameLayout的下标为1的位置开始添加。删除view就简单了,直接移除FrameLayout里面下标为child长度减1的view。首先使用LayoutInflater加载我们的item布局,然后给他设置LayoutParams并且添加到我们的容器下标为1的位置中去。接着根据下标取出对应的bean,然后对item上面的控件进行赋值。里面设置图片的时候使用了一个CatImageLoader.loadImg()方法,这个不是本次的讨论范围,当然你有兴趣下载源码去看下也行。你只要知道,这只是一个提供了对图片进行压缩以及缓存的方法就够了。这里有一点要注意,如果你不对图片进行压缩,那么你在设置图片到imageview上面的时候可能会有一点点卡顿,图片越大越明显,这是不能容忍的。
接着为item设置TouchListener,在按下屏幕down的时候纪录下手指在屏幕中的坐标,然后在move计算用户手指移动的绝对值,并且根据这个值动态设置item的透明度以及旋转角度,并且根据左滑或者右滑显示忽略图片或者感兴趣图片。然后再up的时候判断移动的距离是否超过了我们的界限值,如果超过了,删除该View,如果没超过,对item进行复原,也就是恢复到你按下手指时候的样子。
定义一个方法,用于删除view
public void removeView(final View view, boolean left)
	{
		view.animate()
				.alpha(0)
				.rotation(left ? -90 : 90)
				.setDuration(300).setListener(new AnimatorListenerAdapter()
		{
			@Override
			public void onAnimationEnd(Animator animation)
			{
				mContainer.removeView(view);
				if (mContainer.getChildCount() == 2)//如果只剩一条item和背景图片
				{
					//隐藏背景图片
					mContainer.getChildAt(0).setVisibility(View.GONE);
				} else if ((mContainer.getChildCount() == 1))
				{
					Toast.makeText(MainActivity.this, "已是最后一页...", Toast.LENGTH_SHORT).show();
				}
			}
		});
	}

这个方法看起来比上面那个添加view的代码舒心多了,因为它比较短,嗯,男人总喜欢看到比他短的。删除view非常简单,直接使用FrameLayout的removeView就可以了。不过这里我们不能直接remove,不然会显得非常突兀,我们应该给它设置一个动画,让其在你设置的时间内,透明度变化到0并且根据左滑还是右滑进行旋转,然后在动画结束的时候再进行remove。在item都remove完了时候,记得给用户个提示哦,不然显得非常的不友好。

呼,终于分析完了主界面代码了,接下来说一些小知识点。

多种形状的ImageView

关于这个,同样不是本次的讨论,知道这个的目测也不用我废话了,不知道的自己下载源码看或者网上搜搜吧,网上关于实现圆形图片的要多少有多少,我目前知道的有两种,一种是通过Xfermode,一种是通过BitmapShader,我这里用的是BitmapShader。圆形图片的喔就不说了,这里我给出实现那个下边带弧形的矩形图片的核心代码。
matrix = new Matrix();
bitmapShader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
matrix.setScale(scale, scale);
bitmapShader.setLocalMatrix(matrix);
paint.setShader(bitmapShader);

Path path = new Path();
//贝塞尔曲线的操作点x y
int deviation = 50;//圆弧突起的高度
int controlY = getMeasuredHeight() + deviation;
int controlX = getMeasuredWidth() / 2;
path.moveTo(0, 0);
path.lineTo(getMeasuredWidth(), 0);
path.lineTo(getMeasuredWidth(), getMeasuredHeight() - deviation);
path.quadTo(controlX, controlY, 0, getMeasuredHeight() - deviation);
path.close();
canvas.drawPath(path, paint);

以矩形左上角的点作为起点,弧线是利用贝塞尔曲线绘制的,有学过一点绘图软件的应该不难理解这个,没学过的也没关系,你只要知道,它可以通过起点、终点和操作点,画出一条弧线就好了,具体可以自己用用就知道了。

对ImageView的高斯模糊

虽然安卓有提供一个RenderScript框架可以实现高斯模糊的效果,不过表现不是特别好,在stackoverflow发现了一个更好的对图片进行模糊的类,具体算法太复杂,我们没必要理解,我们能使用就行。
public static Bitmap blur(Bitmap src, View view)
	{
		float scaleFactor = 8;
		float radius = 5;
		Bitmap overlay = Bitmap.createBitmap(
				(int) (view.getMeasuredWidth() / scaleFactor),
				(int) (view.getMeasuredHeight() / scaleFactor),
				Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(overlay);
		canvas.translate(-view.getLeft() / scaleFactor, view.getTop()
				/ scaleFactor);
		canvas.scale(1 / scaleFactor, 1 / scaleFactor);
		Paint paint = new Paint();
		paint.setFlags(Paint.FILTER_BITMAP_FLAG);
		canvas.drawBitmap(src, 0, 0, paint);
		overlay = FastBlur.doBlur(overlay, (int) radius, true);
		return overlay;
	}

直接复制项目文件的FastBlur.java类到你的项目中,然后复制以上代码,传入你要模糊的bitmap,以及你要用户显示bitmap的view就可以完成对bitmap的高斯模糊了。

结束

核心代码都已经贴完了,想看完整的可以通过我下面的链接down下来运行一下,最好是用AndroidStudio,比较方便,用eclipse的话就自己把代码复制过去吧,还有记得资源,都帮你们准备好拉。这效果你们满意了吗?显然没有,有点编程基础的肯定都能看出来,嗯,耦合性太高了,过几天有空我将抽取出一个单独的自定义布局来实现这种效果,以便有更好的复用性。

终于写完了我的第一篇博客,写的可能会有哪里表达的不好,各位前辈或者同辈有什么可以教我的都可以在下面留言哦。

下篇博客点击打开链接


代码地址:https://github.com/Chenantao/ImitateApp


转载注明出处




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值