这篇文章的起因是,群里一个小伙伴去面试时被问到一个效果如何实现: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7POiiFUz-1586686264261)(https://wanandroid.com/blogimgs/81c87faf-49e4-4041-80c8-9e188d1390c4.gif)] 后来鸿洋看到后,就把这个效果放到wanandroid网站上作为一个问题。 然后我用闲暇时间对这个效果做了个简单的实现,本文就是介绍这个实现的思路的。 从图中的效果可以看出,小船是沿着固定的线路在移动,他的移动随着item的移动而移动的,所以我们先实现一个能够沿着固定路径移动的自定义控件,然后把这个控件作为RecyclerView的item就好。 我们先看PathView的实现。 新建一个类继承View,重写起的onDraw方法,然后向外暴露传入图片以及Path路径的方法: 现在问题的关键是,如何通过外界传入一个进度值,将图片画到对应的路径的位置,并将其旋转到位。 这就要借助一个类:PathMeasure,通过其来确定图片的位置以及旋转的角度。 通过如上代码可以获取到path路径在指定百分比位置的坐标以及切线方向。其中pos的两个值分别为位置坐标,而tan的两个值为该点对应切线方向的两个坐标。 所以画具体的图片的方法如上,其中在切线的x分量小于0时要多旋转180度,是图片的朝向是向着左边,也就是x<0的方向。 PathView的完整代码如下: 可以用如下代码测试这个控件: 有了如上控件之后,实现目标效果就相对简单了。 先用第一种方式,每个item都放一个PathView,适配器代码如下: 逻辑很简单,根据奇偶位置来放置两种item就好。 然后最关键的,让小飞机随着RecyclerView的滑动而改变位置,并且只有一个item显示出小飞机,实现如下: 思路是,找到第一个top在某个基线之下的item,然后让他的飞机显示出来,进度的计算就是top与基线的距离与item高度的比值。其中对z的设置是为了让有飞机的item在其他item上面,从而实现飞机的顺畅显示。 最终的效果如下: 第一种实现有个弊端,就是飞机显示出来的只有一个,却在很多个item有了实例,现在用另一种方式来实现,只用一个PathView。 先初始化一个PathView: 然后在进度改变的时候,移动path,并改变progress: 最终实现效果如下: 其中还将小船添加了进去。 完整e的代码参考github小船
前言
PathView的实现
public class PathView extends View { ··· public void setPath(Path path) { mPath = path; } public void setImage(int imageRes) { mBitmap = BitmapFactory.decodeResource(getResources(), imageRes); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawPath(mPath, mPaint); ··· } }
mPathMeasure = new PathMeasure(mPath, false); mLength = mPathMeasure.getLength(); mPathMeasure.getPosTan(progress / mMax * mLength, pos, tan);
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawPath(mPath, mPaint); if (mProgress >= 0) { canvas.save(); canvas.translate(pos[0], pos[1]); double atan = Math.atan(tan[1] / tan[0]); float degrees = (float) (atan * 180 / Math.PI); Log.e(TAG, "degrees " + degrees); degrees += 90;//我选取的图片是朝上的,而Android的极坐标系是朝右的,朝上的角度为-90度 if (tan[0] < 0) { degrees += 180; } canvas.rotate(degrees); canvas.drawBitmap(mBitmap, null, mRect, mPaint); canvas.restore(); } }
public class PathView extends View { public static final String TAG = "PathView"; private Path mPath; private Paint mPaint; private int mProgress = -1; private Bitmap mBitmap; private Rect mRect; private float[] pos = new float[2]; private float[] tan = new float[2]; private PathMeasure mPathMeasure; private float mLength; private float mMax = 100f; public PathView(Context context) { this(context, null); } public PathView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public PathView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); } private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(0xff123456); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(40); mPaint.setStrokeJoin(Paint.Join.ROUND); } public void setPath(Path path) { mPath = path; mPathMeasure = new PathMeasure(mPath, false); mLength = mPathMeasure.getLength(); postInvalidate(); } public void setMax(int max) { mMax = max; } public void setProgress(int progress) { mProgress = progress; if (mPath != null) { mPathMeasure.getPosTan(progress / mMax * mLength, pos, tan); Log.e(TAG, "progress " + progress + " pos " + pos[0] + " " + pos[1] + " " + "tan " + tan[0] + " " + tan[1]); } // postInvalidate(); invalidate(); } public void setImage(int imageRes) { mBitmap = BitmapFactory.decodeResource(getResources(), imageRes); int width = mBitmap.getWidth(); int height = mBitmap.getHeight(); mRect = new Rect(-width / 2, -height / 2, width / 2, height / 2); postInvalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawPath(mPath, mPaint); if (mProgress >= 0) { canvas.save(); canvas.translate(pos[0], pos[1]); double atan = Math.atan(tan[1] / tan[0]); float degrees = (float) (atan * 180 / Math.PI); Log.e(TAG, "pre degrees " + degrees); degrees += 90; if (tan[0] < 0) { degrees = degrees + 180; } Log.e(TAG, "last degrees " + degrees); canvas.rotate(degrees); if (mBitmap != null) { canvas.drawBitmap(mBitmap, null, mRect, mPaint); } canvas.restore(); } } }
final PathView pathView = findViewById(R.id.path_view); Path path = new Path(); path.addCircle(500, 800, 400, Path.Direction.CCW); pathView.setImage(R.mipmap.airplane2); pathView.setPath(path); final Handler handler = new Handler(); handler.post(new Runnable() { private int i = 0; @Override public void run() { pathView.setProgress(i++ % 100); handler.postDelayed(this,50); } });
第一种实现方式
public class PathAdapter extends RecyclerView.Adapter<PathAdapter.PathViewHolder> { private Context mContext; private Path mLeftPath; private Path mRightPath; public PathAdapter(Context context, int width) { mContext = context; initLeftPath(width); initRightPath(width); } @NonNull @Override public PathViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { PathView pathView = new PathView(mContext); pathView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300)); if (viewType == 0) { pathView.setPath(mLeftPath); pathView.setBackgroundColor(0xff134334); } else { pathView.setPath(mRightPath); pathView.setBackgroundColor(0xff752397); } pathView.setImage(R.mipmap.airplane2); return new PathViewHolder(pathView); } private void initLeftPath(int width) { mLeftPath = new Path(); mLeftPath.moveTo(width / 3, 0); mLeftPath.lineTo(width / 3, 250); mLeftPath.lineTo(width / 3 * 2, 250); mLeftPath.lineTo(width / 3 * 2, 300); } private void initRightPath(int width) { mRightPath = new Path(); mRightPath.moveTo(width / 3 * 2, 0); mRightPath.lineTo(width / 3 * 2, 250); mRightPath.lineTo(width / 3, 250); mRightPath.lineTo(width / 3, 300); } @Override public void onBindViewHolder(@NonNull PathViewHolder holder, int position) { } @Override public int getItemViewType(int position) { return position % 2; } @Override public int getItemCount() { return 100; } class PathViewHolder extends RecyclerView.ViewHolder { public PathViewHolder(@NonNull View itemView) { super(itemView); } } }
recyclerView.setAdapter(new PathAdapter(this, (int) (screenWidthDp * scale + 0.5f))); recyclerView.setClipChildren(false); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); Log.e(TAG, "" + dy); int childCount = recyclerView.getChildCount(); for (int i = 0; i < childCount; i++) { PathView child = (PathView) recyclerView.getChildAt(i); float line = (getResources().getConfiguration().screenHeightDp * scale + 0.5f) / 3; Log.e(TAG, "i " + i + " child.getTop() " + child.getTop()); if (child.getTop() > line) { int progress = (int) ((child.getTop() - line) / child.getHeight() * 100); Log.e(TAG, "progress " + progress); if (progress < 0 || progress > 100) { Log.e(TAG, "onScrolled: error progress " + progress); } if (child != currentPathView) { if (currentPathView != null) { child.setProgress(100 - progress); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { currentPathView.setZ(0); } currentPathView.setProgress(-1); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { child.setZ(1); } } else { child.setProgress(100 - progress); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { child.setZ(1); } } currentPathView = child; } else { child.setProgress(100 - progress); } return; } } } });
第二种实现方式
mPathView = findViewById(R.id.path_view); path = new Path(); path.moveTo(screenWidth / 3, 0); for (int i = 0; i < 50; i++) { path.lineTo(screenWidth / 3, 250 + 300 * i * 2); path.lineTo(screenWidth / 3 * 2, 250 + 300 * i * 2); path.lineTo(screenWidth / 3 * 2, 300 + 300 * i * 2); path.lineTo(screenWidth / 3 * 2, 250 + 300 * (i * 2 + 1)); path.lineTo(screenWidth / 3, 250 + 300 * (i * 2 + 1)); path.lineTo(screenWidth / 3, 300 + 300 * (i * 2 + 1)); } mPathView.setPath(path); final int max = 10000; mPathView.setMax(max);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { int scrollY = 0; long time = System.currentTimeMillis(); @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); path.offset(dx, -dy); mPathView.setPath(path); scrollY += dy; int progress = (int) (scrollY / (300 * 100 - screenHeightDp * scale) * max); mPathView.setProgress(progress); Log.e(TAG, "onScrolled: scrollY " + scrollY); Log.e(TAG, "onScrolled: progress " + progress); Log.e(TAG, "onScrolled: progress " + progress); long currentTimeMillis = System.currentTimeMillis(); int dtime = (int) (currentTimeMillis - time); Log.e(TAG, "onScrolled: dtime " + dtime + " dy " + dy); mPathView.setAnimationSpeed(dy / dtime); time = currentTimeMillis; } });
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算