效果图
实现思路
主要通过RecyclerView实现,通过RecyclerView的局部刷新来对item进行属性动画,完成投票
RecyclerView的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/vote_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
tools:itemCount="3"
tools:listitem="@layout/vote_item" />
</LinearLayout>
RecyclerView的item布局
布局思路:
通过嵌套两个矩形view,通过动画来改变第二个view的移动(属性动画实现).里面有些样式我没有贴出来,可以根据自己需求进行改变
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/vote_lay"
android:layout_width="match_parent"
android:layout_height="44dp"
android:layout_marginTop="12dp"
android:background="@drawable/layer_dedee0">
<View
android:id="@+id/vote_result_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/shape_e0ffee_corner8"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" />
<View
android:id="@+id/choose_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/shape_e0ffee_corner8"
android:visibility="gone"
android:layout_margin="1dp"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/vote_tv"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:text="从未在一起"
android:textColor="#333333"
android:textSize="16dp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/choose_vote_tv"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginStart="20dp"
android:text="从未在一起"
android:textColor="#333333"
android:textSize="16dp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"/>
<TextView
android:id="@+id/progress_tv"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:gravity="center"
android:text="60%"
android:textColor="#30D395"
android:textSize="16dp"
android:textStyle="normal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
逻辑代码
数据结构
通过myOption是否为0字段来区分用户是否进行投票,如果myOption大于0就需要根据投票信息的info的id进行区分,来判断用户选中投票的信息
public class VoteInfo {
/**
* 如果用户为0代表为没有选择,如果非0根据optionInfo的id进行区分
*/
private int myOption = 0;
private List<optionInfo> optionInfoList = new ArrayList<>();
public VoteInfo(int myOption, List<optionInfo> optionInfoList) {
this.myOption = myOption;
this.optionInfoList = optionInfoList;
}
public List<optionInfo> getOptionInfoList() {
return optionInfoList;
}
public int getMyOption() {
return myOption;
}
static class optionInfo {
private int id;
private String name = "";
private int rate = 0;
public optionInfo(String name, int rate, int id) {
this.name = name;
this.rate = rate;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getRate() {
return rate;
}
}
}
public class MainActivity extends AppCompatActivity {
private RecyclerView voteRv;
private VoteAdapter voteAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
voteRv = findViewById(R.id.vote_rv);
voteAdapter = new VoteAdapter();
voteRv.setLayoutManager(new LinearLayoutManager(this));
voteRv.setAdapter(voteAdapter);
// 自己编写的数据,可以根据需求从服务器拿去
List<VoteInfo.optionInfo> voteInfoList = new ArrayList<>();
voteInfoList.add(new VoteInfo.optionInfo("一直在一起", 4000, 1));
voteInfoList.add(new VoteInfo.optionInfo("从未在一起", 1000, 2));
voteInfoList.add(new VoteInfo.optionInfo("在一起然后分手", 5000, 3));
VoteInfo voteInfo = new VoteInfo(0, voteInfoList);
voteAdapter.setMyOption(voteInfo.getMyOption());
voteAdapter.setCallBacK((position, voteInfo1) -> {
for (int i = 0; i < voteInfo1.getOptionInfoList().size(); i++) {
if (i != position)
voteAdapter.refreshPartItem(i, voteInfo1.getOptionInfoList());
}
});
voteAdapter.update(voteInfo.getOptionInfoList());
}
}
RecyclerView的适配器
⚠️ ⚠️ ⚠️
如果投票是嵌套在外部RecyclerView的一个holder的时候,
1.注意RecyclerView的复用机制,尽量保证UI的元素有Visible必有gone(成对出现)
2.并且记得改变外部的投票的数据,这样才不会出现当RecyclerVIew滑过投票的时候,然后再次划过来,投票还是会复用之前的样式,而不是点击投票之后的样式!!!
public class VoteAdapter extends RecyclerView.Adapter<VoteAdapter.VoteHolder> {
private List<VoteInfo.optionInfo> voteInfoList = new ArrayList<>();
private VoteHolder.CallBacK callBacK;
private int myOption;
public void setMyOption(int myOption) {
this.myOption = myOption;
}
public void update(List<VoteInfo.optionInfo> list) {
voteInfoList.clear();
voteInfoList.addAll(list);
notifyDataSetChanged();
}
@NonNull
@Override
public VoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VoteHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.vote_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull VoteHolder holder, int position) {
holder.update(voteInfoList.get(position), callBacK, position, myOption);
}
@Override
public void onBindViewHolder(@NonNull VoteHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
} else {
for (Object payload : payloads) {
if (payload instanceof VoteLoad) {
holder.startDefaultAnimation(position, ((VoteLoad) payload).optionList);
}
}
}
}
public void setCallBacK(VoteHolder.CallBacK callBacK) {
this.callBacK = callBacK;
}
public void refreshPartItem(int position, List<VoteInfo.optionInfo> optionList) {
notifyItemChanged(position, new VoteLoad(optionList));
}
private static class VoteLoad {
public List<VoteInfo.optionInfo> optionList = new ArrayList<>();
public VoteLoad(List<VoteInfo.optionInfo> optionList) {
this.optionList.clear();
this.optionList.addAll(optionList);
}
}
@Override
public int getItemCount() {
return voteInfoList.size();
}
public static class VoteHolder extends RecyclerView.ViewHolder {
private final TextView voteTv;
private final TextView progressTv;
private final ConstraintLayout voteLay;
private Animation animation;
private ValueAnimator valueAnimator;
private final View chooseView;
private final TextView chooseVoteTv;
private int x;
public VoteHolder(@NonNull View itemView) {
super(itemView);
voteTv = itemView.findViewById(R.id.vote_tv);
progressTv = itemView.findViewById(R.id.progress_tv);
voteLay = itemView.findViewById(R.id.vote_lay);
chooseView = itemView.findViewById(R.id.choose_view);
chooseVoteTv = itemView.findViewById(R.id.choose_vote_tv);
}
public void update(VoteInfo.optionInfo optionInfo, CallBacK callBacK, int position, int myOption) {
voteTv.setText(optionInfo.getName());
chooseVoteTv.setText(optionInfo.getName());
chooseVoteTv.setVisibility(View.GONE);
voteTv.post(() -> {
int[] location = new int[2];
voteTv.getLocationOnScreen(location);
x = location[0];
});
if (myOption != 0) {
chooseVoteTv.setVisibility(View.VISIBLE);
progressTv.setVisibility(View.VISIBLE);
chooseView.setVisibility(View.VISIBLE);
voteTv.setVisibility(View.GONE);
String progress = (int) (intToDouble(optionInfo.getRate()) * 100) + "%";
progressTv.setText(progress);
voteLay.setBackgroundResource(R.drawable.layer_dedee0);
voteLay.setOnClickListener(v ->
Toast.makeText(chooseView.getContext(), "不可重复投票", Toast.LENGTH_SHORT).show()
);
if (optionInfo.getId() != myOption) {
voteLay.post(() -> {
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) chooseView.getLayoutParams();
layoutParams.width = (int) (voteLay.getWidth() * intToDouble(optionInfo.getRate()));
chooseView.setLayoutParams(layoutParams);
if (optionInfo.getRate() == 0) {
chooseView.setBackgroundResource(R.drawable.shape_ffffff_corner8);
} else {
chooseView.setBackgroundResource(R.drawable.shape_e6e7ec_corner8);
}
progressTv.setTextColor(Color.parseColor("#333333"));
chooseVoteTv.setTextColor(Color.parseColor("#333333"));
});
} else {
voteLay.post(() -> {
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) chooseView.getLayoutParams();
layoutParams.width = (int) (voteLay.getWidth() * intToDouble(optionInfo.getRate()));
voteTv.setVisibility(View.GONE);
chooseVoteTv.setVisibility(View.VISIBLE);
chooseView.setLayoutParams(layoutParams);
voteLay.setBackgroundResource(R.drawable.layer_e0ffee);
chooseView.setBackgroundResource(R.drawable.shape_e0ffee_corner8);
chooseVoteTv.setTextColor(Color.parseColor("#30D395"));
progressTv.setTextColor(Color.parseColor("#30D395"));
});
}
} else {
voteTv.setVisibility(View.VISIBLE);
chooseVoteTv.setVisibility(View.GONE);
progressTv.setVisibility(View.GONE);
chooseVoteTv.setVisibility(View.GONE);
chooseView.setVisibility(View.GONE);
voteLay.setBackgroundResource(R.drawable.layer_dedee0);
voteLay.setOnClickListener(v -> {
List<VoteInfo.optionInfo> voteInfoList = new ArrayList<>();
voteInfoList.add(new VoteInfo.optionInfo("一直在一起", 2000, 1));
voteInfoList.add(new VoteInfo.optionInfo("从未在一起", 2000, 2));
voteInfoList.add(new VoteInfo.optionInfo("在一起然后分手", 6000, 3));
VoteInfo voteInfo = new VoteInfo(1, voteInfoList);
initAnimator(voteLay.getWidth(), intToDouble(voteInfoList.get(position).getRate()));
startChooseAnimation();
if (callBacK != null) {
callBacK.click(position, voteInfo);
}
voteLay.setOnClickListener(null);
});
}
}
public void startDefaultAnimation(int position, List<VoteInfo.optionInfo> options) {
voteLay.setOnClickListener(null);
startAnimationTv();
chooseView.setVisibility(View.VISIBLE);
progressTv.setVisibility(View.VISIBLE);
voteLay.setBackgroundResource(R.drawable.layer_dedee0);
chooseView.setBackgroundResource(R.drawable.shape_e6e7ec_corner8);
progressTv.post(() -> progressTv.setTextColor(Color.parseColor("#333333")));
initAnimator(voteLay.getWidth(), intToDouble(options.get(position).getRate()));
if (valueAnimator != null && options.get(position).getRate() != 0)
valueAnimator.start();
else
chooseView.setBackgroundResource(R.drawable.shape_ffffff_corner8);
}
public void startChooseAnimation() {
voteLay.setBackgroundResource(R.drawable.layer_e0ffee);
chooseView.setVisibility(View.VISIBLE);
progressTv.setVisibility(View.VISIBLE);
voteTv.setTextColor(Color.parseColor("#30D395"));
startAnimationTv();
if (valueAnimator != null)
valueAnimator.start();
}
//文字的动画
private void startAnimationTv() {
animation = new TranslateAnimation(Animation.ABSOLUTE, 0.0f, Animation.ABSOLUTE, -(x - 96), Animation.ABSOLUTE, 0.0f, Animation.ABSOLUTE, 0.0f);
animation.setDuration(700);
animation.setRepeatCount(0);
animation.setInterpolator(new DecelerateInterpolator());
animation.setFillAfter(true);
voteTv.startAnimation(animation);
}
//覆盖在上面的矩形的动画
private void initAnimator(int width, double count) {
String progress = (int) (count * 100) + "%";
progressTv.setText(progress);
valueAnimator = ObjectAnimator.ofFloat(0, (float) (width * count));
valueAnimator.setDuration(700);
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.addUpdateListener(animation -> {
float currentValue = (float) animation.getAnimatedValue();
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) chooseView.getLayoutParams();
layoutParams.width = (int) (currentValue);
chooseView.setLayoutParams(layoutParams);
});
}
public interface CallBacK {
void click(int position, VoteInfo voteInfo);
}
public float intToDouble(int rate) {
float d = (float) rate / 10000;
return (float) Math.round(d * 100) / 100;
}
}
}
GitHub地址
https://github.com/yeluo329/voteView
如果大家觉得可以,就给我点个👍吧.