同事想实现类似这个效果的UI:
上网下载了个ViewPager魔改出来的,结果拖动效果非常不连贯,要不左边要么右边的UI突然异常缩进一下。所以希望我帮忙做一个更好看的,那我试试直接自己弄一个看看?
该控件规律可认为是:有多个图片条目,条目可以滑动时统一拖动,某一条目到中间时最大,越靠左右两边时越小。那么假设最小的条目为0.5f,最大的条目为1f,条目有5个,那么他们的数学比例关系如下:
那么我们继承FrameLayout,做一个自定义控件,可以添加一组ImageView,排成一排,然后按照此规律获取getX相对于屏幕中点的比例进行View的比例调整(调整setScalex、setScaleY),然后当ImageView的getX到了屏幕最左或者最右边时,塞到屏幕的对边控件右侧或左侧,使得ImageView可以轮滚,重复利用,并按照ImageView比例调整图层,最大比例的控件在最上面,最小的在最下面。
原来大概就如同上面所说的那样,代码实现上还有很多细节要进行处理,具体可以看我的代码实现:
我把这个控件叫做BookPageView
package test.graphics.bookPage;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.media.Image;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import cjz.project.maptry.R;
/**
* Created by cjz on 2019/8/8.
*/
public class BookPageView extends FrameLayout{
private boolean initFinished = false;
/**本父空间宽度**/
private int mWidth;
/**本父空间高度**/
private int mHeight;
/**轮播ImageView表**/
private List ivList = new ArrayList<>();
/**上次触摸坐标,移动用**/
private float prevX, prevY;
/**图标最大的比例**/
private float maxScale = 1f;
/**图标最小的比例**/
private float minScale = 0.5f;
/**图标宽度**/
private int unitWidth;
/**图标高度**/
private int unitHeight;
/**11个轮播ImageView**/
private int count = 11;
/**控件大小调整**/
private float sizeRatio = 1.8f;
/**拖动限速**/
private float speedLimit = 50f;
public BookPageView(Context context) {
super(context);
}
public BookPageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BookPageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public List getIvList() {
return ivList;
}
public interface ItemOnTopListener{
/**传入顶部ImageView,方便用户修改图片**/
void topItem(ImageView imageView);
}
private ItemOnTopListener itemOnTopListener;
public ItemOnTopListener getItemOnTopListener() {
return itemOnTopListener;
}
/**控件到达中线(顶部)时回调给同事**/
public void setItemOnTopListener(ItemOnTopListener itemOnTopListener) {
this.itemOnTopListener = itemOnTopListener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!initFinished){
int width = (int)(MeasureSpec.getSize(widthMeasureSpec) * 1f);
int height = (int)(MeasureSpec.getSize(heightMeasureSpec) * 1f);
mWidth = width;
mHeight = height;
this.setLayoutParams(new LayoutParams(width, height));
createView(width, height);
initFinished = true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
prevX = event.getX();
prevY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
translate(event.getX() - prevX, event.getY() - prevY);
prevX = event.getX();
prevY = event.getY();
break;
case MotionEvent.ACTION_UP:
//根据Prevx prevy在ivList哪个控件范围内,然后将该整个对象队列反向移动,到中线居中为止
// for(int i = 0; i < ivList.size(); i++){
// ImageView item = ivList.get(i);
// RectF rect = new RectF(item.getLeft() + (1 - item.getScaleX()) / 2 * item.getWidth(),
// item.getTop() + (1 - item.getScaleY() / 2 * item.getHeight()),
// item.getRight() + (1 - item.getScaleX()) / 2 * item.getWidth() + item.getScaleX() * item.getWidth(),
// item.getTop() + (1 - item.getScaleY()) / 2 * item.getTop() + item.getScaleY() * item.getHeight());
// if(rect.contains(prevX, prevY)){
// float imageViewXCenterPoint = (item.getX() + (1 - item.getScaleX()) / 2 * item.getWidth() + (item.getWidth() * item.getScaleX()) / 2);
// if(im){
//
// }
// }
// }
//自动跳转到最近中线的ImageView
//先找到最近中线的ImageView
float minDistance = Float.MAX_VALUE;
ImageView minItem = null;
boolean isOnRight = false;
for(int i = 0; i < ivList.size(); i++){
ImageView imageView = ivList.get(i);
float imageViewXCenterPoint = (imageView.getX() + (1 - imageView.getScaleX()) / 2 * imageView.getWidth() + (imageView.getWidth() * imageView.getScaleX()) / 2);
float distance = imageViewXCenterPoint - mWidth / 2;
if(Math.abs(distance) < minDistance){
minDistance = Math.abs(distance);
minItem = imageView;
if(distance > 0){
isOnRight = true;
}
}
}
//逐步跳转到中线
float imageViewXCenterPoint = 0;
while (Math.abs(imageViewXCenterPoint - mWidth / 2) > 4){
if(isOnRight){
translate(-4, 0);
} else {
translate(4, 0);
}
imageViewXCenterPoint = (minItem.getX() + (1 - minItem.getScaleX()) / 2 * minItem.getWidth() + (minItem.getWidth() * minItem.getScaleX()) / 2);
}
break;
}
return true;
}
/**移动函数,@param distanceY 备用 **/
public void translate(float distanceX, float distanceY) {
//不让用户拖太快
if(distanceX > speedLimit){
distanceX = speedLimit;
} else if(distanceX < -speedLimit){
distanceX = -speedLimit;
}
float imageViewXCenterPoint;
for (int i = 0; i < ivList.size(); i++) {
ImageView imageView = ivList.get(i);
imageView.setX(imageView.getX() + distanceX);
imageViewXCenterPoint = (imageView.getX() + (1 - imageView.getScaleX()) / 2 * imageView.getWidth() + (imageView.getWidth() * imageView.getScaleX()) / 2);
if (imageViewXCenterPoint < mWidth / 2) { //如果控件越过中线
// float newScale = maxScale * imageViewXCenterPoint / (mWidth / 2);
float newScale = minScale + (maxScale - minScale) * imageViewXCenterPoint / (mWidth / 2);
imageView.setScaleX(newScale);
imageView.setScaleY(newScale);
if(itemOnTopListener != null){
itemOnTopListener.topItem(imageView);
}
} else {
// float newScale = maxScale * (mWidth - imageViewXCenterPoint) / (mWidth / 2);
float newScale = minScale + (maxScale - minScale) * (mWidth - imageViewXCenterPoint) / (mWidth / 2);
imageView.setScaleX(newScale);
imageView.setScaleY(newScale);
if(itemOnTopListener != null){
itemOnTopListener.topItem(imageView);
}
}
if(imageView.getScaleX() < 0.6f){
imageView.setAlpha(0f);
} else {
// imageView.setAlpha(1f);
imageView.setAlpha((float)Math.pow(imageView.getScaleX(), 3));
}
}
//控件循环
for (int i = 0; i < ivList.size(); i++) {
ImageView imageView = ivList.get(i);
if(imageView.getX() + imageView.getWidth() * (1 - imageView.getScaleX()) > mWidth){
imageView.setX(ivList.get(0).getX() - imageView.getWidth() / sizeRatio);
ivList.remove(i);
ivList.add(0, imageView);
break;
} else if(imageView.getX() < 0 - unitWidth / sizeRatio){
imageView.setX(ivList.get(ivList.size() - 1).getX() + ivList.get(ivList.size() - 1).getWidth() / sizeRatio);
ivList.remove(i);
ivList.add(ivList.size(), imageView);
break;
}
}
reSortLayer();
}
/**重新排序图层**/
private void reSortLayer(){
List ivListCopy = new ArrayList<>();
ivListCopy.addAll(ivList);
Collections.sort(ivListCopy, new Comparator() {
@Override
public int compare(ImageView o1, ImageView o2) {
if(o1.getScaleX() > o2.getScaleX()){
return 1;
} else {
return -1;
}
}
});
removeAllViews();
for(int i = 0; i < ivListCopy.size(); i++){
addView(ivListCopy.get(i));
}
}
/**创建图层**/
private void createView(int width, int height) {
unitWidth = (int)(width * sizeRatio / count);
unitHeight = (int) (height * 2 * sizeRatio / count);
for(int i = 0; i < count; i++){
ImageView imageView = new ImageView(getContext());
imageView.setLayoutParams(new ViewGroup.LayoutParams(unitWidth, unitHeight));
addView(imageView);
ivList.add(imageView);
imageView.setTag(new Integer(i));
}
for(int i = 0; i < ivList.size(); i++){
// ivList.get(i).setX(((float)unitWidth / (float)mWidth) * unitWidth * i);
ivList.get(i).setX(((float)mWidth / (float)count) * i - unitWidth * 0.2f);
Log.i("Unit" + i, ivList.get(i).getX() + "");
ivList.get(i).setY(mHeight / 2 - unitHeight / 2);
// ivList.get(i).setY(unitHeight / sizeRatio / 3);
// ivList.get(i).setBackgroundColor(Color.YELLOW);
// ivList.get(i).setImageResource(R.drawable.test1);
// ivList.get(i).setScaleType(ImageView.ScaleType.CENTER);
if(i < ivList.size() / 2){
ivList.get(i).setScaleX(minScale + (maxScale - minScale) * ((i ) / (ivList.size() / 2f)) );
ivList.get(i).setScaleY(minScale + (maxScale - minScale) * ((i ) / (ivList.size() / 2f)) );
Log.i("比例", (minScale + maxScale - minScale) * ((i ) / (ivList.size() / 2f)) + "");
} else {
ivList.get(i).setScaleX(minScale + (maxScale - minScale) * ((ivList.size() - i) / (ivList.size() / 2f)) );
ivList.get(i).setScaleY(minScale +(maxScale - minScale) *((ivList.size() - i) / (ivList.size() / 2f)) );
Log.i("比例", minScale + (maxScale - minScale) * ((ivList.size() - i) / (ivList.size() / 2f)) + "");
}
if(ivList.get(i).getScaleX() < 0.6f){
ivList.get(i).setAlpha(0f);
} else {
// imageView.setAlpha(1f);
ivList.get(i).setAlpha((float)Math.pow(ivList.get(i).getScaleX(), 3));
}
}
reSortLayer();
}
}
MainActivity:
package test.graphics.bookPage;
import android.app.Activity;
import android.media.Image;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import android.widget.SeekBar;
import cjz.project.maptry.R;
/**
* Created by cjz on 2019/8/9.
*/
public class MainActivity extends Activity{
private BookPageView bpv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_book_page);
bpv = findViewById(R.id.bpv);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
int imgSrc[] = new int[]{R.drawable.png1, R.drawable.png2, R.drawable.png3, R.drawable.png4, R.drawable.png5, R.drawable.png6, R.drawable.png7};
for(int i = 0; i < bpv.getIvList().size(); i++){
bpv.getIvList().get(i).setImageResource(imgSrc[i % imgSrc.length]);
}
}
},200);
}
}
实现效果:
实现效果视频:
链接: https://pan.baidu.com/s/1uSRaTL1WMiqp9kg_QYp4DA 提取码: 2mx7 复制这段内容后打开百度网盘手机App,操作更方便哦