最近公司项目里增加了一个评价功能模块,就要用到咱们最常见的ratingBar了,大家都知道系统自带的ratingBar有多丑,所以打算自定义,然后翻看资料,说是在drawable里写个.xml文件,例如这样:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background" android:drawable="@drawable/startnormal"/>
<item android:id="@android:id/progress" android:drawable="@drawable/startslected"/>
</layer-list>
然后在布局文件里android:progressDrawable="@drawable/rating_style",运行试试,恩,还不错,等等下面的流苏是什么鬼??
换个图片试试,恩这次好了,可是在不同分辨率的手机上
显示的大小不一致,恩,可能还是图片的问题,可是公司美工不给出适配各个手机分辨率的图,如果你们公司图片给好几套,那么这个方法是可行的,可以满足开发需求。
我选择了自定义控件来解决,效果图如下(模拟器有点卡,真机运行效果还不错):
下面来说说思路,我选择的是组合控件方法来实现这个自定义控件,也就是利用在linearlayout中摆放imageview,实现步骤是:
1.为每一个星星设置drawable,并且计算出每个星星的起始位置并保存在集合中;
2.重写onTouchEvent方法,
当手指按下时:记录按下的横坐标downX,将所有星星设置为未选中状态,然后判断所有星星的起始坐标是否小于downX,如果小于则将星星改为选中状态;
当手指滑动时:记录每次滑动到的横坐标moveX,如果moveX大于当前最后一个选中状态星星的坐标,则是向右滑动,否则向左滑动,然后根据滑动距离除上
每两颗星星之间的滑动距离计算出滑动了几颗星星,然后改变星星的对应状态;
听起来很简单是不是?
下面就是实现代码+注释
<declare-styleable name="CustomRatingBar"> <attr name="crating" format="integer"/> <attr name="cstartDrawable" format="reference"/> <attr name="cselectedDrawable" format="reference"/> <attr name="cspace" format="dimension"/> <attr name="cisIndicator" format="boolean"/> <attr name="cstartNum" format="integer"/> </declare-styleable>
package com.example.ratingbar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.widget.ImageView; import android.widget.LinearLayout; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Created by pactera on 2017/5/11. * 自定义ratingbar,系统自带的ratingbar很难控制图片大小,希望自定义的控件可以根据图片大小设置控件大小 * ,并且可以实现点击选中以及滑动选中的功能 */ public class CustomRatingBar extends LinearLayout { /**星星的个数*/ private int num; /**未选中的星星图片*/ private Drawable startDrawable; /**选中的星星图片*/ private Drawable selectedDrawable; /**选中的星星个数*/ private int rating; /**星星的间隔*/ private float space; /**是否是指示器,即是否可选*/ private boolean indicator; /**盛放星星的imageview的集合*/ private ArrayList<ImageView> startContains; /**盛放星星位置的集合*/ private ArrayList<Integer> locations; /**每一个星星控件的布局参数*/ private LayoutParams params; public CustomRatingBar(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } /**从布局文件中读取数据*/ private void init(Context context, AttributeSet attrs) { setOrientation(LinearLayout.HORIZONTAL); setGravity(Gravity.CENTER_VERTICAL); TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.CustomRatingBar); num = arr.getInteger(R.styleable.CustomRatingBar_cstartNum, 5); startDrawable = arr.getDrawable(R.styleable.CustomRatingBar_cstartDrawable); selectedDrawable = arr.getDrawable(R.styleable.CustomRatingBar_cselectedDrawable); rating = arr.getInt(R.styleable.CustomRatingBar_crating,0); space = arr.getDimension(R.styleable.CustomRatingBar_cspace, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,getResources().getDisplayMetrics())); indicator = arr.getBoolean(R.styleable.CustomRatingBar_cisIndicator,false); arr.recycle(); startContains = new ArrayList<>(); locations = new ArrayList<>(); params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.leftMargin = (int) space;//设置左边距 int locationLeft = 0; for (int i=0; i<num; i++) { ImageView iv = new ImageView(context); if (i < rating) { iv.setImageDrawable(selectedDrawable);//设置选中背景 } else { iv.setImageDrawable(startDrawable);//设置未选中背景 } iv.setLayoutParams(params); addView(iv); startContains.add(iv); locationLeft = (int) (i*startDrawable.getIntrinsicWidth() + (i+1)*space); // Log.i("zhangdi","locationLeft="+locationLeft+", intrinsicwidth="+startDrawable.getIntrinsicWidth()); locations.add(locationLeft);//设置每个星星左侧的位置坐标 } } private float downX; private float moveX; @Override public boolean onTouchEvent(MotionEvent event) { if (indicator) { return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: downX = event.getX(); Log.i("zhangdi","downX="+downX); handleDown(downX); break; case MotionEvent.ACTION_MOVE: moveX = event.getX() - downX; Log.i("zhangdi","moveX="+moveX+", x="+event.getX()); if (Math.abs(moveX) > (startDrawable.getIntrinsicWidth()/2)) { handleMove(event.getX()); } break; case MotionEvent.ACTION_UP: break; } return true; } /**按下时判断按的位置*/ private void handleDown(float downX) { rating = 0; for (ImageView iv: startContains) {//所有星星重置为未选状态 iv.setImageDrawable(startDrawable); } //遍历所有星星的位置,将星星设置为选中状态并且设置rating的值直到星星所在位置大于按下位置为止 for (int i=0; i<startContains.size(); i++) { if (locations.get(i) > downX) { break; } startContains.get(i).setImageDrawable(selectedDrawable); if (rating < (num - 1)) {//注意不要让下标越界 rating++; } } } /**滑动控件设置是否选中*/ private void handleMove(float moveX) { int move = 0; int unit = 0; Drawable drawable = null; Log.i("zhangdi","rating="+rating); if (moveX > locations.get(rating)) {//向右滑动 //startDrawable.getIntrinsicWidth()/2+space为一颗星星与下一颗的距离,移动距离除上两个星星间的距离, // 计算出移动了几颗星星 move = (int) ((moveX-locations.get(rating))/(startDrawable.getIntrinsicWidth()/2+space)); Log.i("zhangdi","向右滑move="+move); unit = 1; drawable = selectedDrawable; } else {//向左滑动 move = (int) ((moveX-locations.get(rating)-startDrawable.getIntrinsicWidth()/2) /(startDrawable.getIntrinsicWidth()/2+space)); Log.i("zhangdi","向左滑move="+move); unit = -1; drawable = startDrawable; } //根据滑动过了几颗星星设置星星的背景图片和rating if (move != 0) { for (int i = 0; i < Math.abs(move); i++) { startContains.get(rating).setImageDrawable(drawable); if (unit > 0 && rating < (num - 1)) { rating += unit; } else if (unit < 0 && rating >0) { rating += unit; } } } } }
使用方法
<com.example.ratingbar.CustomRatingBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="120dp" app:cstartDrawable="@drawable/rating_star" app:cselectedDrawable="@drawable/rating_star_select" app:cisIndicator="false" android:layout_marginLeft="10dp" app:crating="3"/>