1.前言
最近有朋友问我些自定义控件的一些问题,又问我关于无限轮播viewpager的实现方式,有空之余就编写篇blog来讲解下吧,希望对你们有帮助。本篇文章只要讲解些开发中常用的一些自定义控件view的demo,本人酷爱cs但工作之后就好几年没碰了,就用手游枪击作为图片demo做为缅怀(*^-^*)。文章最后附带demo下载链接地址。
文章总体实现无限轮播并触碰停止轮播的viewpage、水平和垂直滚动的TextView、仿QQ滑动删除、下拉刷新上拉加载view、毛玻璃效果、低版本水波纹、圆环头像图片。
View的绘制过程图:
View的事件分发机制图:
2.无限轮播viewPager
效果图:
实现代码:
package com.example.lainanzhou.customviewdemo.activity;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.example.lainanzhou.customviewdemo.R;
import com.example.lainanzhou.customviewdemo.util.DimensUtil;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* 无限轮播的Viewpager
* <p/>
* Created by Joker on 2016/6/30.
*/
public class ViewPagerActivity extends Activity implements ViewPager.OnPageChangeListener {
@BindView(R.id.viewPager)
ViewPager mViewPager;
@BindView(R.id.picture_container)
LinearLayout mPictureContainer;
private int[] mPicturesId = {R.mipmap.one, R.mipmap.tow, R.mipmap.three, R.mipmap.four, R.mipmap.five, R.mipmap.six};
private Handler mMainHandler;
private AutoSwitchTask mSwitchTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_viewpager);
ButterKnife.bind(this);
initData();
initEvent();
}
private void initData() {
mViewPager.setAdapter(new PictureAdapter());
mMainHandler = new Handler(getMainLooper());
}
private void initEvent() {
// 设置viewpager的监听
mViewPager.setOnPageChangeListener(this);
// 设置ViewPager中间页(避免从0开始角标越界)
int middle = Integer.MAX_VALUE / 2;
int extra = middle % mPicturesId.length;
mViewPager.setCurrentItem(middle - extra);
// 给容器添加点
addPictureContainer();
//开启自动轮播任务
startAutoSwitchTask();
}
private void startAutoSwitchTask() {
// 开始轮播任务
if (mSwitchTask == null) {
mSwitchTask = new AutoSwitchTask();
}
mSwitchTask.start();
// 给ViewPager设置touch的监听:但手指触摸时停止轮播页面
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//停止轮播
mSwitchTask.stop();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//开启轮播
mSwitchTask.start();
break;
default:
break;
}
return false;
}
});
}
class AutoSwitchTask implements Runnable {
//开始轮播
public void start() {
stop();
mMainHandler.postDelayed(this, 3000);
}
//停止轮播
public void stop() {
mMainHandler.removeCallbacks(this);
}
@Override
public void run() {
int item = mViewPager.getCurrentItem();
mViewPager.setCurrentItem(++item);
mMainHandler.postDelayed(this, 3000);
}
}
//添加点
private void addPictureContainer() {
mPictureContainer.removeAllViews();
for (int i = 0; i < mPicturesId.length; i++) {
View view = new View(this);
view.setBackgroundResource(R.mipmap.indicator_normal);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(DimensUtil.dip2px(6), DimensUtil.dip2px(6));
if (i != 0) {
params.leftMargin = DimensUtil.dip2px(8);
} else {
view.setBackgroundResource(R.mipmap.indicator_selected);// 设置默认选中
}
mPictureContainer.addView(view, params);
}
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
/**
* 页面给选中时回调
*
* @param position
*/
@Override
public void onPageSelected(int position) {
position = position % mPicturesId.length;
int count = mPictureContainer.getChildCount();
for (int i = 0; i < count; i++) {
View view = mPictureContainer.getChildAt(i);
view.setBackgroundResource(i == position ? R.mipmap.indicator_selected
: R.mipmap.indicator_normal);
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
private class PictureAdapter extends PagerAdapter {
/**
* 设置最大页面数量,实现无限轮播
*
* @return
*/
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
/**
* 复用view对象
*
* @param view
* @param object
* @return
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
/**
* 初始化显示的view
*
* @param container
* @param position
* @return
*/
@Override
public Object instantiateItem(ViewGroup container, int position) {
position = position % mPicturesId.length;
ImageView iv = new ImageView(ViewPagerActivity.this);
iv.setScaleType(ImageView.ScaleType.FIT_XY);
iv.setImageResource(mPicturesId[position]);
container.addView(iv);
return iv;
}
/**
* 回收view
*
* @param container
* @param position
* @param object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
}
3.水平滚动和垂直滚动的TextView的实现
效果图:
实现代码:
package com.example.lainanzhou.customviewdemo.activity;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.example.lainanzhou.customviewdemo.R;
import com.example.lainanzhou.customviewdemo.view.MarqueeTextView;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* TODO:
* 水平和垂直滚动的TextView
*
* @author Joker
* @createDate 2016/7/5.
*/
public class MarqueeTextViewActivity extends Activity {
@BindView(R.id.marqueeTextView1)
MarqueeTextView mMarqueeTextView1;
@BindView(R.id.marqueeTextView2)
MarqueeTextView mMarqueeTextView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_marqueetextview);
ButterKnife.bind(this);
initEvent();
}
private void initEvent() {
setMarqueeTextView1();
setMarqueeTextView2();
}
private void setMarqueeTextView2() {
ViewGroup.MarginLayoutParams margin = new ViewGroup.MarginLayoutParams(
mMarqueeTextView2.getLayoutParams());
margin.setMargins(50, 50, 50, 0);//设置滚动区域位置
//测量marqueeTextView宽高,在没有展示之前获取宽高
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mMarqueeTextView2.measure(w, h);
int height = mMarqueeTextView2.getMeasuredHeight();
int width = mMarqueeTextView2.getMeasuredWidth();
//必须要设置布局,不然没法显示
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(margin);
layoutParams.height = height;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
mMarqueeTextView2.setLayoutParams(layoutParams);
mMarqueeTextView2.setScrollWidth(1000);
mMarqueeTextView2.setScrollHeight(height);
mMarqueeTextView2.setCurrentPosition(0);//设置滚动信息从滚动区域的右边出来
mMarqueeTextView2.setSpeed(2);
mMarqueeTextView2.setText("这才是真正的垂直跑马灯效果!");
mMarqueeTextView2.setBackgroundColor(Color.parseColor("#dddddd"));
}
private void setMarqueeTextView1() {
ViewGroup.MarginLayoutParams margin = new ViewGroup.MarginLayoutParams(
mMarqueeTextView1.getLayoutParams());
margin.setMargins(50, 50, 50, 0);//设置滚动区域位置
//测量marqueeTextView宽高,在没有展示之前获取宽高
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mMarqueeTextView1.measure(w, h);
int height = mMarqueeTextView1.getMeasuredHeight();
int width = mMarqueeTextView1.getMeasuredWidth();
//必须要设置布局,不然没法显示
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(margin);
layoutParams.height = height;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
mMarqueeTextView1.setLayoutParams(layoutParams);
mMarqueeTextView1.setScrollWidth(1000);
mMarqueeTextView1.setScrollHeight(height);
mMarqueeTextView1.setCurrentPosition(0);//设置滚动信息从滚动区域的右边出来
mMarqueeTextView1.setSpeed(2);
mMarqueeTextView1.setText("这才是真正的水平跑马灯效果!");
mMarqueeTextView1.setBackgroundColor(Color.parseColor("#dddddd"));
}
}
4.仿QQ滑动删除
效果图:
实现代码:
package com.example.lainanzhou.customviewdemo.activity;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.example.lainanzhou.customviewdemo.R;
import com.example.lainanzhou.customviewdemo.view.SlipView;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* TODO:
* 滑动删除item
*
* @author Joker
* @createDate 2016/7/5.
*/
public class SlipViewActivity extends Activity implements AbsListView.OnScrollListener {
@BindView(R.id.listView)
ListView mListView;
private List<String> mDatas = new ArrayList<>();
//记录打开的view
private List<SlipView> mOpenedViews = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_slipview);
ButterKnife.bind(this);
initData();
}
private void initData() {
for (int i = 0; i < 50; i++) {
mDatas.add(" 内容--" + i);
}
// 设置adapter
mListView.setAdapter(new MyAdapter());
// 设置listView的滑动监听
mListView.setOnScrollListener(this);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING
|| scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
closeOpenedView();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return mDatas == null ? 0 : mDatas.size();
}
@Override
public Object getItem(int position) {
return mDatas == null ? null : mDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(SlipViewActivity.this, R.layout.item,
null);
holder.sv = (SlipView) convertView.findViewById(R.id.sv);
holder.tv = (TextView) convertView.findViewById(R.id.tv);
holder.delete = convertView.findViewById(R.id.delete);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv.setText(mDatas.get(position));
// 监听sweepView
holder.sv.setOnSweepListener(new SlipView.OnSweepListener() {
@Override
public void onOpen(SlipView view) {
// 先关闭已经打开的View
closeOpenedView();
synchronized (mOpenedViews) {
mOpenedViews.add(view);
}
}
@Override
public void onClose(SlipView view) {
synchronized (mOpenedViews) {
mOpenedViews.remove(view);
}
}
});
holder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDatas.remove(position);
notifyDataSetChanged();
}
});
return convertView;
}
}
private void closeOpenedView() {
ListIterator<SlipView> iterator = mOpenedViews.listIterator();
while (iterator.hasNext()) {
SlipView next = iterator.next();
next.close();
}
}
class ViewHolder {
SlipView sv;
TextView tv;
View delete;
}
}
5.下拉刷新上拉加载更多
效果图:
代码实现 :
(1).没携带头view的RefreshListView
package com.example.lainanzhou.customviewdemo.activity;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.lainanzhou.customviewdemo.R;
import com.example.lainanzhou.customviewdemo.view.RefreshListView;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* TODO:
* 实现自定义下拉刷新和加载更多的activity
*
* @author Joker
* @createDate 2016/7/5.
*/
public class RefreshListViewActivity extends Activity implements RefreshListView.OnRefreshListener {
@BindView(R.id.refreshlistview)
RefreshListView mRefreshlistview;
private List<String> textList = new ArrayList<>();
private MyAdapter adapter;
private int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refreshlistview);
ButterKnife.bind(this);
initData();
}
private void initData() {
for (int i = 0; i < 30; i++) {
textList.add("内容" + i);
}
adapter = new MyAdapter();
mRefreshlistview.setAdapter(adapter);
mRefreshlistview.setOnRefreshListener(this);
}
public void click(View view){
Intent intent = new Intent(this,RefreshListViewActivity2.class);
startActivity(intent);
}
@Override
public void onDownPullRefresh() {//下拉刷新回调
new AsyncTask<String, Integer, Void>() {
@Override
protected Void doInBackground(String... params) {
SystemClock.sleep(2000);
count = 0;
textList.clear();
for (int i = 0; i < 30; i++) {
textList.add("内容" + i);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
adapter.notifyDataSetChanged();
mRefreshlistview.hideHeaderView();
}
}.execute(new String[]{});
}
@Override
public void onLoadingMore() {//上拉加载更多回调
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(2000);
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
return null;
}
@Override
protected void onPostExecute(Void result) {
adapter.notifyDataSetChanged();
mRefreshlistview.hideFooterView();
}
}.execute(new Void[]{});
}
class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return textList.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = new TextView(RefreshListViewActivity.this);
tv.setText(textList.get(position));
tv.setTextSize(18);
tv.setTextColor(Color.BLACK);
return tv;
}
}
}
(2).携带头 View的RefreshListView
package com.example.lainanzhou.customviewdemo.activity;
import android.app.Activity;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.lainanzhou.customviewdemo.R;
import com.example.lainanzhou.customviewdemo.view.RefreshListView2;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* TODO:
* 第二种自定义携带头布局的下拉刷新上拉加载更多的ListView
*
* @author Joker
* @createDate 2016/7/5.
*/
public class RefreshListViewActivity2 extends Activity implements RefreshListView2.OnRefreshListener, AdapterView.OnItemClickListener {
@BindView(R.id.refreshlistview2)
RefreshListView2 mRefreshlistview2;
private List<String> textList = new ArrayList<>();
private ListDataAdapter mTextAdapter;
private int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refreshlistview2);
ButterKnife.bind(this);
initData();
initEvent();
}
private void initEvent() {
// 给ListView加载自定义的头布局
TextView hearView = new TextView(this);
hearView.setText("我是头view");
hearView.setTextSize(24);
hearView.setTextColor(Color.parseColor("#ff0000"));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
hearView.setLayoutParams(params);
mRefreshlistview2.addCustomHeaderView(hearView);
// 设置刷新监听
mRefreshlistview2.setOnRefreshListener(this);
// 设置item的监听
mRefreshlistview2.setOnItemClickListener(this);
}
private void initData() {
for (int i = 0; i < 30; i++) {
textList.add("内容" + i);
}
mTextAdapter = new ListDataAdapter();
mRefreshlistview2.setAdapter(mTextAdapter);
}
@Override
public void onRefreshing() {
new AsyncTask<String, Integer, Void>() {
@Override
protected Void doInBackground(String... params) {
SystemClock.sleep(2000);
count = 0;
textList.clear();
for (int i = 0; i < 30; i++) {
textList.add("内容" + i);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
mRefreshlistview2.setRereshTime(System.currentTimeMillis());
mTextAdapter.notifyDataSetChanged();
mRefreshlistview2.setRefreshFinish();
}
}.execute(new String[]{});
}
@Override
public void onLoadMore() {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(2000);
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
textList.add("添加内容" + (++count));
return null;
}
@Override
protected void onPostExecute(Void result) {
mTextAdapter.notifyDataSetChanged();
mRefreshlistview2.setRefreshFinish();
}
}.execute(new Void[]{});
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
class ListDataAdapter extends BaseAdapter {
@Override
public int getCount() {
return textList.size();
}
@Override
public Object getItem(int position) {
return textList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = new TextView(RefreshListViewActivity2.this);
tv.setText(textList.get(position));
tv.setTextSize(18);
tv.setTextColor(Color.BLACK);
return tv;
}
}
}
6.仿苹果毛玻璃效果
效果图:
实现代码:
package com.example.lainanzhou.customviewdemo.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
import com.example.lainanzhou.customviewdemo.R;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* TODO:
* 展示毛玻璃效果的activity
* 1.针对API16以上可以使用Android自带api RenderScript去实现
* 2.对于api16以下的适配,不过性能会比较低
* 3.使用c语言实现:使用了jni方式调用实现
* <p/>
* github上有个开源框架挺好的,地址:https://github.com/kikoso/android-stackblur
* 另项目中附上,其实就是使用jni调用c实现
*
* @author Joker
* @createDate 2016/7/5.
*/
public class BlurViewActivity extends Activity {
@BindView(R.id.sb)
SeekBar mSb;
@BindView(R.id.iv)
ImageView mIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blurview);
ButterKnife.bind(this);
initData();
}
private void initData() {
mSb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
onBlur();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
private void onBlur() {
if (mSb.getProgress() <= 0 || mSb.getProgress() >= 25)
return;
int radius = mSb.getProgress();
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.mipmap.android_platform_256);
mIv.setImageBitmap(getBlurBitmap(this, bitmap, radius));
// mIv.setImageBitmap(fastblur(this, bitmap, radius));
}
public void click2GitHub(View view) {
Intent intent = new Intent(this, BlurViewFromGithubActivity.class);
startActivity(intent);
}
/**
* @param context
* @param sentBitmap
* @param radius 模糊半径(模糊度)不能小于0和大于25
* @return
*/
private Bitmap getBlurBitmap(Context context, Bitmap sentBitmap, int radius) {
//针对api16以上的处理方式
if (Build.VERSION.SDK_INT > 16) {
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
final RenderScript rs = RenderScript.create(context);
final Allocation input = Allocation.createFromBitmap(rs, sentBitmap, Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(radius);//e.g. 3.f
script.setInput(input);
script.forEach(output);
output.copyTo(bitmap);
return bitmap;
}
return fastblur(context, sentBitmap, radius);
}
/**
* 针对api16以下的处理方式
*
* @param context
* @param sentBitmap
* @param radius
* @return
*/
public Bitmap fastblur(Context context, Bitmap sentBitmap, int radius) {
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int temp = 256 * divsum;
int dv[] = new int[temp];
for (i = 0; i < temp; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16)
| (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
}
7.适配低版本的水波纹
效果图:
代码实现:
(1).继承LinearLayout实现
package com.example.lainanzhou.customviewdemo.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import com.example.lainanzhou.customviewdemo.R;
import java.util.ArrayList;
/**
* TODO:
* 自定义继承LinearLayout的水波纹效果view
* 注意:
* 该方式实现的水波纹无法在listView中使用,会无法实现listView里面item点击事件,因为拦截掉了
* 且该方式实现方式是作为父容器将所有的子控件都包裹在里面,不然会出现点击处理逻辑错乱
* 针对这几点缺点,后面又搞了个WaterRelativeLayout进行适配listView
*
* @author Joker
* @createDate 2016/7/6.
*/
public class WaterLinearLayout extends LinearLayout implements Runnable {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mTargetWidth;
private int mTargetHeight;
private int mMinBetweenWidthAndHeight;
private int mMaxBetweenWidthAndHeight;
private int mMaxRevealRadius;
private int mRevealRadiusGap;
private int mRevealRadius = 0;
private float mCenterX;
private float mCenterY;
private int[] mLocationInScreen = new int[2];
private boolean mShouldDoAnimation = false;
private boolean mIsPressed = false;
private int INVALIDATE_DURATION = 40;//播放水波纹持续时间
private View mTouchTarget;
private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable();
public WaterLinearLayout(Context context) {
super(context);
init();
}
public WaterLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public WaterLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setWillNotDraw(false);
mPaint.setColor(getResources().getColor(R.color.water_color));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.getLocationOnScreen(mLocationInScreen);
}
private void initParametersForChild(MotionEvent event, View view) {
mCenterX = event.getX();
mCenterY = event.getY();
mTargetWidth = view.getMeasuredWidth();
mTargetHeight = view.getMeasuredHeight();
mMinBetweenWidthAndHeight = Math.min(mTargetWidth, mTargetHeight);
mMaxBetweenWidthAndHeight = Math.max(mTargetWidth, mTargetHeight);
mRevealRadius = 0;
mShouldDoAnimation = true;
mIsPressed = true;
mRevealRadiusGap = mMinBetweenWidthAndHeight / 8;
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0] - mLocationInScreen[0];
int transformedCenterX = (int) mCenterX - left;
mMaxRevealRadius = Math.max(transformedCenterX, mTargetWidth - transformedCenterX);
}
/**
* 实现绘制圆动画
*
* @param canvas
*/
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {
return;
}
if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {
mRevealRadius += mRevealRadiusGap * 4;
} else {
mRevealRadius += mRevealRadiusGap;
}
this.getLocationOnScreen(mLocationInScreen);
int[] location = new int[2];
mTouchTarget.getLocationOnScreen(location);
int left = location[0] - mLocationInScreen[0];
int top = location[1] - mLocationInScreen[1];
int right = left + mTouchTarget.getMeasuredWidth();
int bottom = top + mTouchTarget.getMeasuredHeight();
canvas.save();
canvas.clipRect(left, top, right, bottom);
canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);
canvas.restore();
if (mRevealRadius <= mMaxRevealRadius) {
//postInvalidate是在非UI线程中更新UI的方法
postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
} else if (!mIsPressed) {
mShouldDoAnimation = false;
postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
}
}
/**
* 拦截处理touch事件
*
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
View touchTarget = getTouchTarget(this, x, y);
if (touchTarget != null && touchTarget.isClickable() && touchTarget.isEnabled()) {
mTouchTarget = touchTarget;
initParametersForChild(event, touchTarget);
postInvalidateDelayed(INVALIDATE_DURATION);
}
} else if (action == MotionEvent.ACTION_UP) {
mIsPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION);
mDispatchUpTouchEventRunnable.event = event;
postDelayed(mDispatchUpTouchEventRunnable, 40);
return true;
} else if (action == MotionEvent.ACTION_CANCEL) {
mIsPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION);
}
return super.dispatchTouchEvent(event);
}
private View getTouchTarget(View view, int x, int y) {
View target = null;
ArrayList<View> TouchableViews = view.getTouchables();
for (View child : TouchableViews) {
if (isTouchPointInView(child, x, y)) {
target = child;
break;
}
}
return target;
}
/**
* 判断子控件是否可点击和获取点击子控件的坐标点
*
* @param view
* @param x
* @param y
* @return
*/
private boolean isTouchPointInView(View view, int x, int y) {
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0];
int top = location[1];
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
if (view.isClickable() && y >= top && y <= bottom
&& x >= left && x <= right) {
return true;
}
return false;
}
@Override
public boolean performClick() {
postDelayed(this, 400);
return true;
}
@Override
public void run() {
super.performClick();
}
private class DispatchUpTouchEventRunnable implements Runnable {
public MotionEvent event;
@Override
public void run() {
if (mTouchTarget == null || !mTouchTarget.isEnabled()) {
return;
}
if (isTouchPointInView(mTouchTarget, (int) event.getRawX(), (int) event.getRawY())) {
mTouchTarget.performClick();//分发处理点击子控件view的事件
}
}
}
}
(2).继承RelateLayout实现
package com.example.lainanzhou.customviewdemo.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.ColorRes;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.AdapterView;
import android.widget.RelativeLayout;
import com.example.lainanzhou.customviewdemo.R;
/**
* TODO:
* 继承RelativeLayout实现自定义水波纹效果的view
* 可以作为listView里面的item的父容器使用
*
* @author Joker
* @createDate 2016/7/6.
*/
public class WaterRelativeLayout extends RelativeLayout {
private int WIDTH;
private int HEIGHT;
private int frameRate = 10;
private int rippleDuration = 400;
private int rippleAlpha = 90;
private Handler canvasHandler;
private float radiusMax = 0;
private boolean animationRunning = false;
private int timer = 0;
private int timerEmpty = 0;
private int durationEmpty = -1;
private float x = -1;
private float y = -1;
private int zoomDuration;
private float zoomScale;
private ScaleAnimation scaleAnimation;
private Boolean hasToZoom;
private Boolean isCentered;
private Integer rippleType;
private Paint paint;
private Bitmap originBitmap;
private int rippleColor;
private int ripplePadding;
private GestureDetector gestureDetector;
private final Runnable runnable = new Runnable() {
@Override
public void run() {
invalidate();
}
};
private OnRippleCompleteListener onCompletionListener;
public WaterRelativeLayout(Context context) {
super(context);
}
public WaterRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public WaterRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
/**
* 初始化些配置信息的方法
*
* @param context
* @param attrs
*/
private void init(final Context context, final AttributeSet attrs) {
if (isInEditMode())
return;
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView);
rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.water_color));
rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0);
hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false);
isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false);
rippleDuration = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, rippleDuration);
frameRate = typedArray.getInteger(R.styleable.RippleView_rv_framerate, frameRate);
rippleAlpha = typedArray.getInteger(R.styleable.RippleView_rv_alpha, rippleAlpha);
ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0);
canvasHandler = new Handler();
zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f);
zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200);
typedArray.recycle();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(rippleColor);
paint.setAlpha(rippleAlpha);
this.setWillNotDraw(false);
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent event) {
super.onLongPress(event);
animateRipple(event);
sendClickEvent(true);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
});
this.setDrawingCacheEnabled(true);
this.setClickable(true);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (animationRunning) {
canvas.save();
if (rippleDuration <= timer * frameRate) {
animationRunning = false;
timer = 0;
durationEmpty = -1;
timerEmpty = 0;
if (Build.VERSION.SDK_INT != 23) {
canvas.restore();
}
invalidate();
if (onCompletionListener != null)
onCompletionListener.onComplete(this);
return;
} else
canvasHandler.postDelayed(runnable, frameRate);
if (timer == 0)
canvas.save();
canvas.drawCircle(x, y, (radiusMax * (((float) timer * frameRate) / rippleDuration)), paint);
paint.setColor(Color.parseColor("#ffff4444"));
if (rippleType == 1 && originBitmap != null && (((float) timer * frameRate) / rippleDuration) > 0.4f) {
if (durationEmpty == -1)
durationEmpty = rippleDuration - timer * frameRate;
timerEmpty++;
final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate) / (durationEmpty))));
canvas.drawBitmap(tmpBitmap, 0, 0, paint);
tmpBitmap.recycle();
}
paint.setColor(rippleColor);
if (rippleType == 1) {
if ((((float) timer * frameRate) / rippleDuration) > 0.6f)
paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate) / (durationEmpty)))));
else
paint.setAlpha(rippleAlpha);
} else
paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate) / rippleDuration))));
timer++;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
WIDTH = w;
HEIGHT = h;
scaleAnimation = new ScaleAnimation(1.0f, zoomScale, 1.0f, zoomScale, w / 2, h / 2);
scaleAnimation.setDuration(zoomDuration);
scaleAnimation.setRepeatMode(Animation.REVERSE);
scaleAnimation.setRepeatCount(1);
}
/**
* Launch Ripple animation for the current view with a MotionEvent
*
* @param event MotionEvent registered by the Ripple gesture listener
*/
public void animateRipple(MotionEvent event) {
createAnimation(event.getX(), event.getY());
}
/**
* 动画效果
*
* @param x 动画x轴坐标中心点
* @param y 动画y轴坐标中心点
*/
public void animateRipple(final float x, final float y) {
createAnimation(x, y);
}
/**
* 创建动画效果
*
* @param x x轴水平中心坐标点
* @param y y轴垂直中心坐标点
*/
private void createAnimation(final float x, final float y) {
if (this.isEnabled() && !animationRunning) {
if (hasToZoom)
this.startAnimation(scaleAnimation);
radiusMax = Math.max(WIDTH, HEIGHT);
if (rippleType != 2)
radiusMax /= 2;
radiusMax -= ripplePadding;
if (isCentered || rippleType == 1) {
this.x = getMeasuredWidth() / 2;
this.y = getMeasuredHeight() / 2;
} else {
this.x = x;
this.y = y;
}
animationRunning = true;
if (rippleType == 1 && originBitmap == null)
originBitmap = getDrawingCache(true);
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
animateRipple(event);
sendClickEvent(false);
}
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
this.onTouchEvent(event);
return super.onInterceptTouchEvent(event);
}
/**
* 处理点击事件
*
* @param isLongClick 是否是长点击事件
*/
private void sendClickEvent(final Boolean isLongClick) {
if (getParent() instanceof AdapterView) {
final AdapterView adapterView = (AdapterView) getParent();
final int position = adapterView.getPositionForView(this);
final long id = adapterView.getItemIdAtPosition(position);
if (isLongClick) {
if (adapterView.getOnItemLongClickListener() != null)
adapterView.getOnItemLongClickListener().onItemLongClick(adapterView, this, position, id);
} else {
if (adapterView.getOnItemClickListener() != null)
adapterView.getOnItemClickListener().onItemClick(adapterView, this, position, id);
}
}
}
private Bitmap getCircleBitmap(final int radius) {
final Bitmap output = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
final Rect rect = new Rect((int) (x - radius), (int) (y - radius), (int) (x + radius), (int) (y + radius));
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
canvas.drawCircle(x, y, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(originBitmap, rect, rect, paint);
return output;
}
/**
* 设置水波纹的颜色值
*
* @param rippleColor
*/
@ColorRes
public void setRippleColor(int rippleColor) {
this.rippleColor = getResources().getColor(rippleColor);
}
public int getRippleColor() {
return rippleColor;
}
public RippleType getRippleType() {
return RippleType.values()[rippleType];
}
/**
* 设置水波纹类型
*
* @param rippleType
*/
public void setRippleType(final RippleType rippleType) {
this.rippleType = rippleType.ordinal();
}
public Boolean isCentered() {
return isCentered;
}
/**
* 设置中心播放动画view
*
* @param isCentered
*/
public void setCentered(final Boolean isCentered) {
this.isCentered = isCentered;
}
public int getRipplePadding() {
return ripplePadding;
}
/**
* 设置填充
*
* @param ripplePadding
*/
public void setRipplePadding(int ripplePadding) {
this.ripplePadding = ripplePadding;
}
public Boolean isZooming() {
return hasToZoom;
}
/**
* 设置焦点
*
* @param hasToZoom 默认false
*/
public void setZooming(Boolean hasToZoom) {
this.hasToZoom = hasToZoom;
}
public float getZoomScale() {
return zoomScale;
}
/**
* 设置缩放
*
* @param zoomScale 默认1.0f
*/
public void setZoomScale(float zoomScale) {
this.zoomScale = zoomScale;
}
public int getZoomDuration() {
return zoomDuration;
}
/**
* 设置持续时长
*
* @param zoomDuration 默认 200ms
*/
public void setZoomDuration(int zoomDuration) {
this.zoomDuration = zoomDuration;
}
public int getRippleDuration() {
return rippleDuration;
}
/**
* 设置动画持续时长
*
* @param rippleDuration 默认 400ms
*/
public void setRippleDuration(int rippleDuration) {
this.rippleDuration = rippleDuration;
}
public int getFrameRate() {
return frameRate;
}
/**
* 设置水波纹帧
*
* @param frameRate 默认 10
*/
public void setFrameRate(int frameRate) {
this.frameRate = frameRate;
}
public int getRippleAlpha() {
return rippleAlpha;
}
/**
* 设置水波纹颜色透明值
*
* @param rippleAlpha 取值 0 ~ 255, 默认 90
*/
public void setRippleAlpha(int rippleAlpha) {
this.rippleAlpha = rippleAlpha;
}
public void setOnRippleCompleteListener(OnRippleCompleteListener listener) {
this.onCompletionListener = listener;
}
/**
* 水波纹结束动画的回调方法
*/
public interface OnRippleCompleteListener {
void onComplete(WaterRelativeLayout rippleView);
}
/**
* 枚举类型
*/
public enum RippleType {
SIMPLE(0),
DOUBLE(1),
RECTANGLE(2);
int type;
RippleType(int type) {
this.type = type;
}
}
}
8.圆环头像图片实现
效果图:
代码实现:
/*
* Copyright 2014 - 2015 Henning Dodenhof
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.lainanzhou.customviewdemo.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.widget.ImageView;
import com.example.lainanzhou.customviewdemo.R;
/**
* TODO:
* 实现圆环头像的imageView
*
* @author Joker
*/
public class CircleImageView extends ImageView {
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private static final int COLORDRAWABLE_DIMENSION = 2;
private static final int DEFAULT_BORDER_WIDTH = 0;
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
private static final boolean DEFAULT_BORDER_OVERLAY = false;
private final RectF mDrawableRect = new RectF();
private final RectF mBorderRect = new RectF();
private final Matrix mShaderMatrix = new Matrix();
private final Paint mBitmapPaint = new Paint();
private final Paint mBorderPaint = new Paint();
private final Paint mFillPaint = new Paint();
private int mBorderColor = DEFAULT_BORDER_COLOR;
private int mBorderWidth = DEFAULT_BORDER_WIDTH;
private int mFillColor = DEFAULT_FILL_COLOR;
private Bitmap mBitmap;
private BitmapShader mBitmapShader;
private int mBitmapWidth;
private int mBitmapHeight;
private float mDrawableRadius;
private float mBorderRadius;
private ColorFilter mColorFilter;
private boolean mReady;
private boolean mSetupPending;
private boolean mBorderOverlay;
public CircleImageView(Context context) {
super(context);
init();
}
public CircleImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);
a.recycle();
init();
}
private void init() {
super.setScaleType(SCALE_TYPE);
mReady = true;
if (mSetupPending) {
setup();
mSetupPending = false;
}
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}
@Override
public void setScaleType(ScaleType scaleType) {
if (scaleType != SCALE_TYPE) {
throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
}
}
@Override
public void setAdjustViewBounds(boolean adjustViewBounds) {
if (adjustViewBounds) {
throw new IllegalArgumentException("adjustViewBounds not supported.");
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mBitmap == null) {
return;
}
if (mFillColor != Color.TRANSPARENT) {
canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mFillPaint);
}
canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mDrawableRadius, mBitmapPaint);
if (mBorderWidth != 0) {
canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, mBorderRadius, mBorderPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setup();
}
public int getBorderColor() {
return mBorderColor;
}
public void setBorderColor(@ColorInt int borderColor) {
if (borderColor == mBorderColor) {
return;
}
mBorderColor = borderColor;
mBorderPaint.setColor(mBorderColor);
invalidate();
}
public void setBorderColorResource(@ColorRes int borderColorRes) {
setBorderColor(getContext().getResources().getColor(borderColorRes));
}
public int getFillColor() {
return mFillColor;
}
public void setFillColor(@ColorInt int fillColor) {
if (fillColor == mFillColor) {
return;
}
mFillColor = fillColor;
mFillPaint.setColor(fillColor);
invalidate();
}
public void setFillColorResource(@ColorRes int fillColorRes) {
setFillColor(getContext().getResources().getColor(fillColorRes));
}
public int getBorderWidth() {
return mBorderWidth;
}
public void setBorderWidth(int borderWidth) {
if (borderWidth == mBorderWidth) {
return;
}
mBorderWidth = borderWidth;
setup();
}
public boolean isBorderOverlay() {
return mBorderOverlay;
}
public void setBorderOverlay(boolean borderOverlay) {
if (borderOverlay == mBorderOverlay) {
return;
}
mBorderOverlay = borderOverlay;
setup();
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mBitmap = bm;
setup();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
setup();
}
@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
mBitmap = uri != null ? getBitmapFromDrawable(getDrawable()) : null;
setup();
}
@Override
public void setColorFilter(ColorFilter cf) {
if (cf == mColorFilter) {
return;
}
mColorFilter = cf;
mBitmapPaint.setColorFilter(mColorFilter);
invalidate();
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private void setup() {
if (!mReady) {
mSetupPending = true;
return;
}
if (getWidth() == 0 && getHeight() == 0) {
return;
}
if (mBitmap == null) {
invalidate();
return;
}
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
mFillPaint.setStyle(Paint.Style.FILL);
mFillPaint.setAntiAlias(true);
mFillPaint.setColor(mFillColor);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(0, 0, getWidth(), getHeight());
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay) {
mDrawableRect.inset(mBorderWidth, mBorderWidth);
}
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
updateShaderMatrix();
invalidate();
}
private void updateShaderMatrix() {
float scale;
float dx = 0;
float dy = 0;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
} else {
scale = mDrawableRect.width() / (float) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
}
最后附带项目下载链接地址:点击打开项目代码demo链接
后期若开发遇到常用的功能将会继续添加,若项目中出现有问题也欢迎大神指点一二。