android 卡片布局翻转,Android开发之实现CardView翻转和切换

1.先看下实现的效果图

AAffA0nNPuCLAAAAAElFTkSuQmCC

页面失效效果图

AAffA0nNPuCLAAAAAElFTkSuQmCC

运行的动态效果图

2.具体实现的方法和步骤

(1).代码架构图

AAffA0nNPuCLAAAAAElFTkSuQmCC

代码结构图

(2).在这里需要引入第三方的jar包

nineoldandroids-2.4.0.jar这个可以在网上下载到

(3).实现自定义的卡片控件CardView,继承FrameLayout控件类,具体实现的代码如下

public class CardView extends FrameLayout {

private static final int ITEM_SPACE = 40;

private static final int DEF_MAX_VISIBLE = 4;

private int mMaxVisible = DEF_MAX_VISIBLE;

private int itemSpace = ITEM_SPACE;

private float mTouchSlop;

private ListAdapter mListAdapter;

private int mNextAdapterPosition;

private SparseArray viewHolder = new SparseArray();

private OnCardClickListener mListener;

private int topPosition;

private Rect topRect;

public interface OnCardClickListener {

void onCardClick(View view, int position);

}

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

super(context, attrs, defStyle);

init();

}

public CardView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

public CardView(Context context) {

super(context);

init();

}

private void init() {

topRect = new Rect();

ViewConfiguration con = ViewConfiguration.get(getContext());

mTouchSlop = con.getScaledTouchSlop();

}

public void setMaxVisibleCount(int count) {

mMaxVisible = count;

}

public int getMaxVisibleCount() {

return mMaxVisible;

}

public void setItemSpace(int itemSpace) {

this.itemSpace = itemSpace;

}

public int getItemSpace() {

return itemSpace;

}

public ListAdapter getAdapter() {

return mListAdapter;

}

public void setAdapter(ListAdapter adapter) {

if (mListAdapter != null) {

mListAdapter.unregisterDataSetObserver(mDataSetObserver);

}

mNextAdapterPosition = 0;

mListAdapter = adapter;

adapter.registerDataSetObserver(mDataSetObserver);

removeAllViews();

ensureFull();

}

public void setOnCardClickListener(OnCardClickListener listener) {

mListener = listener;

}

private void ensureFull() {

while (mNextAdapterPosition < mListAdapter.getCount()

&& getChildCount() < mMaxVisible) {

int index = mNextAdapterPosition % mMaxVisible;

View convertView = viewHolder.get(index);

final View view = mListAdapter.getView(mNextAdapterPosition,

convertView, this);

view.setOnClickListener(null);

viewHolder.put(index, view);

// 添加剩余的View时,始终处在最后

index = Math.min(mNextAdapterPosition, mMaxVisible - 1);

ViewHelper.setScaleX(view,((mMaxVisible - index - 1) / (float) mMaxVisible) * 0.2f + 0.8f);

int topMargin = (mMaxVisible - index - 1) * itemSpace;

ViewHelper.setTranslationY(view, topMargin);

ViewHelper.setAlpha(view, mNextAdapterPosition == 0 ? 1 : 0.5f);

LayoutParams params = (LayoutParams) view.getLayoutParams();

if (params == null) {

params = new LayoutParams(LayoutParams.MATCH_PARENT,

LayoutParams.WRAP_CONTENT);

}

addViewInLayout(view, 0, params);

mNextAdapterPosition += 1;

}

// requestLayout();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int childCount = getChildCount();

int maxHeight = 0;

int maxWidth = 0;

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

View child = getChildAt(i);

this.measureChild(child, widthMeasureSpec, heightMeasureSpec);

int height = child.getMeasuredHeight();

int width = child.getMeasuredWidth();

if (height > maxHeight) {

maxHeight = height;

}

if (width > maxWidth) {

maxWidth = width;

}

}

int desireWidth = widthSize;

int desireHeight = heightSize;

if (widthMode == MeasureSpec.AT_MOST) {

desireWidth = maxWidth + getPaddingLeft() + getPaddingRight();

}

if (heightMode == MeasureSpec.AT_MOST) {

desireHeight = maxHeight + (mMaxVisible - 1) * itemSpace + getPaddingTop() + getPaddingBottom();

}

setMeasuredDimension(desireWidth, desireHeight);

}

@Override

protected void onLayout(boolean changed, int left, int top, int right,

int bottom) {

super.onLayout(changed, left, top, right, bottom);

View topView = getChildAt(getChildCount() - 1);

if (topView != null) {

topView.setOnClickListener(listener);

}

}

float downX, downY;

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_MOVE:

if (goDown()) {

downY = -1;

}

break;

}

return super.onTouchEvent(event);

}

/**

* 下移所有视图

*/

private boolean goDown() {

final View topView = getChildAt(getChildCount() - 1);

if(!topView.isEnabled()){

return false;

}

// topView.getHitRect(topRect); 在4.3以前有bug,用以下方法代替

topRect = getHitRect(topRect, topView);

// 如果按下的位置不在顶部视图上,则不移动

if (!topRect.contains((int) downX, (int) downY)) {

return false;

}

topView.setEnabled(false);

ViewPropertyAnimator anim = ViewPropertyAnimator

.animate(topView)

.translationY(

ViewHelper.getTranslationY(topView)

+ topView.getHeight()).alpha(0).scaleX(1)

.setListener(null).setDuration(200);

anim.setListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

topView.setEnabled(true);

removeView(topView);

ensureFull();

final int count = getChildCount();

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

final View view = getChildAt(i);

float scaleX = ViewHelper.getScaleX(view)

+ ((float) 1 / mMaxVisible) * 0.2f;

float tranlateY = ViewHelper.getTranslationY(view)

+ itemSpace;

if (i == count - 1) {

bringToTop(view);

} else {

if ((count == mMaxVisible && i != 0)

|| count < mMaxVisible) {

ViewPropertyAnimator

.animate(view)

.translationY(tranlateY)

.setInterpolator(

new AccelerateInterpolator())

.setListener(null).scaleX(scaleX)

.setDuration(200);

}

}

}

}

});

return true;

}

/**

* 将下一个视图移到前边

*

* @param view

*/

private void bringToTop(final View view) {

topPosition++;

float scaleX = ViewHelper.getScaleX(view) + ((float) 1 / mMaxVisible)

* 0.2f;

float tranlateY = ViewHelper.getTranslationY(view) + itemSpace;

ViewPropertyAnimator.animate(view).translationY(tranlateY)

.scaleX(scaleX).setDuration(200).alpha(1)

.setInterpolator(new AccelerateInterpolator());

}

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

float currentY = ev.getY();

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

downX = ev.getX();

downY = ev.getY();

break;

case MotionEvent.ACTION_MOVE:

float distance = currentY - downY;

if (distance > mTouchSlop) {

return true;

}

break;

}

return false;

}

public static Rect getHitRect(Rect rect, View child) {

rect.left = child.getLeft();

rect.right = child.getRight();

rect.top = (int) (child.getTop() + ViewHelper.getTranslationY(child));

rect.bottom = (int) (child.getBottom() + ViewHelper

.getTranslationY(child));

return rect;

}

private final DataSetObserver mDataSetObserver = new DataSetObserver() {

@Override

public void onChanged() {

super.onChanged();

}

@Override

public void onInvalidated() {

super.onInvalidated();

}

};

private OnClickListener listener = new OnClickListener() {

@Override

public void onClick(View v) {

if (mListener != null) {

mListener.onCardClick(v, topPosition);

}

}

};

}

(4).实现自定义的卡片控件之后,下面需要为这个CardView实现适配器Adapter了,在此我们定义的是一个抽象Adapter类,需要用户根据自己的实际业务场景继承这个抽象类即可,具体代码如下:

public abstract class CardAdapter extends BaseAdapter {

private final Context mContext;

private ArrayList mData;

public CardAdapter(Context context) {

mContext = context;

mData = new ArrayList();

}

public CardAdapter(Context context, Collection extends T> items) {

mContext = context;

mData = new ArrayList(items);

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

FrameLayout wrapper = (FrameLayout) convertView;

View cardView;

View convertedCardView;

if (wrapper == null) {

wrapper = new FrameLayout(mContext);

wrapper.setBackgroundResource(R.drawable.card_background_shadow);

cardView = getCardView(position, null, wrapper);

wrapper.addView(cardView);

} else {

cardView = wrapper.getChildAt(0);

convertedCardView = getCardView(position, cardView, wrapper);

//要先删除,然后再添加,否则界面不更新

wrapper.removeView(cardView);

wrapper.addView(convertedCardView);

if (convertedCardView != cardView) {

}

}

return wrapper;

}

protected abstract View getCardView(int position, View convertView, ViewGroup parent);

public void addAll(List items){

mData.addAll(items);

}

@Override

public T getItem(int position) {

return mData.get(position);

}

@Override

public int getCount() {

return mData.size();

}

@Override

public long getItemId(int position) {

return getItem(position).hashCode();

}

public Context getContext() {

return mContext;

}

public void clear(){

if(mData != null){

mData.clear();

}

}

}

(5).在这个卡片控件实现过程中,需要根据手机屏幕大小来自适应卡片控件的大小,所以需要一个方法来计算手机屏幕大小,我们抽象成一个工具类来实现,代码如下:

public class Utils {

public float convertPixelsToDp(Context ctx, float px) {

DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();

float dp = px / (metrics.densityDpi / 160f);

return dp;

}

public static int convertDpToPixelInt(Context context, float dp) {

DisplayMetrics metrics = context.getResources().getDisplayMetrics();

int px = (int) (dp * (metrics.densityDpi / 160f));

return px;

}

public static float convertDpToPixel(Context context, float dp) {

DisplayMetrics metrics = context.getResources().getDisplayMetrics();

float px = (float) (dp * (metrics.densityDpi / 160f));

return px;

}

}

(6).在Activity来实现卡片布局的显示了,很快就能看到具体的实现效果了。

public class MainActivity extends FragmentActivity implements OnCardClickListener{

List list;

private TestFragment frag;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initUI();

}

private void initUI() {

CardView cardView = (CardView) findViewById(R.id.cardView1);

cardView.setOnCardClickListener(this);

cardView.setItemSpace(Utils.convertDpToPixelInt(this, 20));

MyCardAdapter adapter = new MyCardAdapter(this);

adapter.addAll(initData());

cardView.setAdapter(adapter);

FragmentManager manager = getSupportFragmentManager();

frag = new TestFragment();

manager.beginTransaction().add(R.id.contentView, frag).commit();

}

@Override

public void onCardClick(final View view, final int position) {

Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();

Bundle bundle = new Bundle();

bundle.putString("text", list.get(position%list.size()));

frag.show(view,bundle);

}

private List initData() {

list = new ArrayList();

list.add("七仔:");

list.add("楠楠:");

list.add("小编:");

list.add("陈儿:");

list.add("小小:");

list.add("Zz:");

list.add("牵着蜗牛去西藏:");

return list;

}

public class MyCardAdapter extends CardAdapter{

public MyCardAdapter(Context context) {

super(context);

}

@Override

public int getCount() {

return Integer.MAX_VALUE;

}

@Override

protected View getCardView(int position,

View convertView, ViewGroup parent) {

if(convertView == null) {

LayoutInflater inflater = LayoutInflater.from(MainActivity.this);

convertView = inflater.inflate(R.layout.item_layout, parent, false);

}

TextView tv = (TextView) convertView.findViewById(R.id.textView1);

String text = getItem(position%list.size());

tv.setText(text);

return convertView;

}

}

}

(7).在点击每个卡片的时候有个翻转的页面,我们是用Fragment来实现的,具体的Fragment代码如下:

public class TestFragment extends Fragment{

private TextView tv;

private View root;

private View view;

@Override

public View onCreateView(LayoutInflater inflater,

@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

root = inflater.inflate(R.layout.frag_layout, container,false);

initUI(root);

return root;

}

private void initUI(final View root) {

root.setClickable(true);

root.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {}

});

tv = (TextView) root.findViewById(R.id.textView);

root.findViewById(R.id.button).setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

root.setClickable(false);

ViewPropertyAnimator.animate(root)

.rotationY(-90).setDuration(200)

.setListener(new AnimatorListenerAdapter(){

@Override

public void onAnimationEnd(Animator animation) {

root.clearAnimation();

root.setVisibility(View.INVISIBLE);

view.setEnabled(true);

}

});

}

});

}

public void show(final View view,Bundle bundle){

view.setEnabled(false);

this.view = view;

String text = bundle.getString("text");

tv.setText(text);

ViewHelper.setRotationY(view, 0);

ViewHelper.setRotationY(root, -90);

root.setVisibility(View.VISIBLE);

ViewPropertyAnimator.animate(view).rotationY(90)

.setDuration(300).setListener(null)

.setInterpolator(new AccelerateInterpolator());

ViewPropertyAnimator.animate(root)

.rotationY(0).setDuration(200).setStartDelay(300)

.setListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

ViewHelper.setRotationY(view, 0);

}

});

}

}

3.下面给出具体的资源文件

(1).layout布局文件

item_layout.xml的具体布局代码如下:

android:layout_width="match_parent"

android:layout_height="match_parent"

>

android:id="@+id/textView1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:gravity="center"

android:padding="20dp"

android:textAppearance="?android:attr/textAppearanceMedium"

/>

android:id="@+id/imageView1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_below="@+id/textView1"

android:text="@string/text"

android:textAppearance="?android:attr/textAppearanceMedium"

android:gravity="center"

android:layout_marginTop="30dp"

android:layout_marginBottom="30dp"

/>

activity_main的布局文件代码如下:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.DevStoreDemo.MainActivity"

android:background="#00688B"

android:clipChildren="false" >

android:id="@+id/cardView1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:clipChildren="false"/>

android:id="@+id/contentView"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical"/>

frag_layout.xml布局文件代码如下:

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@android:color/white"

android:visibility="invisible"

>

android:id="@+id/textView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:textAppearance="?android:attr/textAppearanceLarge"

/>

android:id="@+id/button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentLeft="true"

android:layout_alignParentTop="true"

android:class="lazyload" src="https://img-blog.csdnimg.cn/2022010619011019967.png" data-original="@drawable/close_bg"

android:padding="10dp"

/>

android:id="@+id/imageView1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:layout_centerVertical="true"

android:class="lazyload" src="https://img-blog.csdnimg.cn/2022010619011019967.png" data-original="@drawable/a" />

(2)values资源文件内容

carcontainer_attrs.xml资源文件代码如下

color.xml资源文件代码如下

#EBEBEB

#D0D0CE

(3)drawable资源文件主要存放一些图片资源

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

4.以上是卡片控件CardView的一个简单的实现,有不足的地方请大家多多指教,谢谢

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值