(原创)自定义view(view的绘制过程)、无限轮播并触碰停止轮播的viewpage、水平和垂直滚动的TextView、仿QQ滑动删除、下拉刷新上拉加载view、毛玻璃效果、低版本水波纹、圆环头像图
来源:互联网 发布:淘宝皮草店装修效果图 编辑:程序博客网 时间:2024/05/16 09:33
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链接
后期若开发遇到常用的功能将会继续添加,若项目中出现有问题也欢迎大神指点一二。
0 0
- (原创)自定义view(view的绘制过程)、无限轮播并触碰停止轮播的viewpage、水平和垂直滚动的TextView、仿QQ滑动删除、下拉刷新上拉加载view、毛玻璃效果、低版本水波纹、圆环头像图
- XlistView的上拉刷新下拉加载 和Fragment 和无限轮播
- RecyclerView的上拉加载下拉刷新和viewpager自动有点无限轮播
- PullToRefresh下拉刷新、上拉加载更多ViewPage+Handler完成无限轮播
- 自定义View之无限轮播Banner的实现:STBanner
- 自定义View之无限轮播Banner的实现:STBanner
- ionic的侧滑,无限轮播,上拉刷新下拉加载
- android自定义View-垂直滚动的TextView
- android自定义View-垂直滚动的TextView
- android自定义View-垂直滚动的TextView
- 自定义View-垂直滚动的TextView
- 自定义下拉刷新上拉加载View
- 安卓listView实现下拉刷新上拉加载滑动仿QQ的删除功能
- Android 中所有View的上拉加载下拉刷新
- 自定义view实现无限轮播
- Android开发之ViewPager实现轮播图(轮播广告)效果的自定义View
- Android开发之ViewPager实现轮播图(轮播广告)效果的自定义View
- Android开发之ViewPager实现轮播图(轮播广告)效果的自定义View
- SQL查询某字段数据相同的数据
- DDL,DML
- MapGIS6.7_学习中遇到的问题(6):点位置坐标转为属性
- 每日一得--spring事物service切面不使用事物注解
- Intent实现页间面跳转
- (原创)自定义view(view的绘制过程)、无限轮播并触碰停止轮播的viewpage、水平和垂直滚动的TextView、仿QQ滑动删除、下拉刷新上拉加载view、毛玻璃效果、低版本水波纹、圆环头像图
- WinDbg配置和使用基础
- iOS 3D Touch
- SQL分页的应用
- KMP_next数组_while详解_Java实现
- CentOS6.5升级内核
- xml之DOM方式解析,DOM4J工具解析原理
- 布隆过滤器(Bloom Filter)详解
- k短路 SPFA+A*算法 poj 2449