先看效果图:
下面一步步介绍如何实现:
首先,这是一个动态的效果,很消耗性能,所以我们用SurfaceView,而且我们要用到线程:
public class ShineView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
还有SurfaceView相关的SurfaceHolder,及线程相关变量:
private SurfaceHolder mHolder;
private Thread thread;
private boolean isRun;
public ShineView(Context context) {
super(context);
mHolder=getHolder();
mHolder.addCallback(this);
}
public ShineView(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder=getHolder();
mHolder.addCallback(this);
}
我们要可以设置光源的位置,光源的背景色,光源的颜色,及光源的数量:
private float x,y;//光源坐标
private boolean hasInitXY;//光源是否初始化
private int lightSize=1;//光线数量
private int bgColor=Color.WHITE;
private int lightColor=Color.GREEN;
/**
* 设置背景颜色
* @param bgColor 背景颜色
*/
public void setBgColor(int bgColor) {
this.bgColor = bgColor;
}
/**
* 设置光线颜色
* @param lightColor 光线颜色
*/
public void setLightColor(int lightColor) {
this.lightColor = lightColor;
}
/**
* 设置光源在控件内的坐标位置X,Y。不设置,默认光源在控件的中心。
* @param x 控件内x坐标
* @param y 控件内y坐标
*/
public void setLightXY(float x,float y){
this.x=x;
this.y=y;
hasInitXY=true;
}
我们默认如果不设置光源的坐标,光源就在中心,所以上面加上hasInitXY判断。
然后我们测量光源在控件内的位置,我们不允许光源超出控件外。我们还要加上一个偏移位置,计算光源不在中心时的情况:
private float detalX,detalY;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(x>getMeasuredWidth())x=getMeasuredWidth();
if(x<0)x=0;
if(y>getMeasuredHeight())y=getMeasuredHeight();
if(y<0)y=0;
float centerX=getMeasuredWidth()/2;
float centerY=getMeasuredHeight()/2;
if(hasInitXY){
detalX=x-centerX;
detalY=y-centerY;
}else{
detalX=0;
detalY=0;
}
}
我们发出的光线其实是一个弧形,因此有光线射出时的角度startRange,弧度的角度大小sweepAngle:
private float startRange;//
private float sweepAngle=45;//角度
我们要在360度里平均分布光线数量,因此360/lightSize是平均分布的空间,也是从一条光线到另一条光线的角度。
我们为了后面用for循环绘制光线的方便,定义一个增量的角度:
private float addRange;//增量
而光线只能占分布空间的一半,不能占全部,否则就填满阴影看不到背景了。
我们也定义至少一条光线,因此设置光线数量的方法如下:
/**
* 设置光线数量
* @param lightSize 光线数量
*/
public void setLightSize(int lightSize){
if(lightSize<1){
lightSize=1;//最少一条光线
sweepAngle=45;//弧度最大45
}
this.lightSize=lightSize;
this.addRange=360/lightSize;
this.sweepAngle=this.addRange/2;
}
然后我们在SurfaceCreated方法里开线程绘图了:
Paint p= null;
RectF oval = null;
@Override
public void surfaceCreated(SurfaceHolder holder){
p=new Paint();
p.setColor(lightColor);
oval= new RectF(-getWidth()+detalX, -getHeight()+detalY, getWidth()*2+detalX, getHeight()*2+detalY);
isRun=true;
thread=new Thread(this);
thread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRun=false;
}
我们也定义光线可以顺时针或逆时针转动,默认是顺时针:
public static final int CLOCKWISE=1;//顺时针
public static final int ANTICLOCKWISE=2;//逆时针
private int rotationDirection=CLOCKWISE;
/**
* 设置转动方向。不设置,默认是顺时针。
* @param rotationDirection 转动方向
*/
public void setRotationDirection(int rotationDirection) {
this.rotationDirection = rotationDirection;
}
然后,我们在run()里不断循环绘图:
@Override
public void run() {
while(isRun){
Canvas canvas=mHolder.lockCanvas();
canvas.drawColor(bgColor);
for(int i=0;i<lightSize;i++){
canvas.drawArc(oval, startRange+addRange*i, sweepAngle, true, p);
}
mHolder.unlockCanvasAndPost(canvas);
SystemClock.sleep(50);//设置更小的数值可以加快速度
if(rotationDirection==ANTICLOCKWISE){
startRange-=1;
}else{
startRange+=1;
}
this.postInvalidate();
}
}
至此就完成了。
详细项目:
https://github.com/zhengjingle/ShineView