ConstraintLayout 使用
相对位置属性(layout_constraint[自身控件位置]_[目标控件位置]="[目标控件ID]")、Margin属性、goneMargin属性、Bias属性(在对齐父容器后,设置水平与竖直的比例)、Ratio属性(layout_constraintDimensionRatio设置View的高宽比)、Chains链状结构(layout_constraintHorizontal_chainStyle、layout_constraintVertical_chainStyle...)、Guideline
RecyclerView 使用
可通过设置LayoutManager来快速实现ListView、GridView、瀑布流的效果,横向纵向显示、ItemAnimation
RecyclerView 多Item布局实现
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return super.onCreateViewHolder(parent, viewType);
}
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
@Override
public int getViewTypeCount() {
return super.getViewTypeCount();
}
列表界面的封装(分页、上拉加载)
// fragment基类
public abstract class BaseFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(returnFragmentLayout(), container, false);
ButterKnife.bind(this, rootView);
return rootView;
}
public abstract
@LayoutRes
int returnFragmentLayout();
public abstract void viewCreated();
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewCreated();// 此处调用初始化View方法
}
}
// 列表Adapter基类
public abstract class BasePageListAdapter extends RecyclerView.Adapter {
/**
* item类型
*/
public static final int ITEM = 1;
public static final int FOOTER = 2;
@IntDef({NULL, LOADING, LOADMORE, COMPLETE, CUSTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface LoadState {
}
...
public BasePageListAdapter(Context context, @LayoutRes int itemResourceId, List data) {
this.context = context;
this.itemResourceId = itemResourceId;
setItemResourceId();
}
/**
* if use the constructor with param of the resource iD of item layout, you can ignore this method,
* else the resource AGENCY_ID of item layout should be assigned to itemResourceId ;
*
* @see #itemResourceId
*/
public abstract void setItemResourceId();
...
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if (position == getItemCount() - 1 && loadingState != NULL) {
// footer 样式
...
if (customFooterView != null && customFooterView.getVisibility() == View.VISIBLE) {
// 下一页有数据了
customFooterView.setVisibility(View.GONE);
}
switch (loadingState) {
case LOADING:
tip.setVisibility(View.VISIBLE);
tip.setText("拼命加载中...");
progressBar.setVisibility(View.VISIBLE);
break;
case LOADMORE:
...
break;
case COMPLETE:
...
break;
case CUSTOM:
if (customFooterView != null) {
// 数据已加载完并为自定义Footer
customFooterView.setVisibility(View.VISIBLE);
tip.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
if (footerViewHolder.itemView.findViewWithTag("footer") == null) {
...
((LinearLayout) footerViewHolder.itemView).addView(customFooterView);
}
}
break;
}
} else if (position < data.size()) {
distributeData(viewHolder, data.get(position));
}
}
public abstract void distributeData(ItemViewHolder viewHolder, T data);
/**
* this BaseRecycleAdapter include two type of view-- normal item and footer;
*
* @param position get item type with the position
* @return type
*/
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1 && loadingState != NULL) return FOOTER;
else return ITEM;
}
@Override
public int getItemCount() {
int footer = loadingState != NULL ? 1 : 0;
return data.size() + footer;
}
// 自定义 Footer
public void setCustomFooter(View customFooterView) {
this.customFooterView = customFooterView;
this.customFooterView.setTag("footer");
setLoadingState(CUSTOM);
notifyDataSetChanged();
}
/**
* check if RecyclerView is ready to callback the method-- onLoadNext,
* you should call this method before you want to callback
* {@link com.tech2real.listener.RecyclerLoadNextListener.OnLoadNextListener}
*
* @param ready is the RecycleView ready to call method onLoadNext()
*/
public void setReadyToLoadNext(boolean ready) {
this.ready = ready;
}
public boolean isReadyToLoadNext() {
return ready;
}
...
}
// 列表页抽象类
public abstract class BaseListFragment extends BaseFragment implements BasePageListAdapter.OnItemClickListener,
SwipeRefreshLayout.OnRefreshListener, RecyclerLoadNextListener.OnLoadNextListener {
...
@BindView(R.id.recycler)
public RecyclerView mRecycler;
protected BasePageListAdapter adapter;
@IntDef({NULL, SEARCH, ERROR, NORMAL, CUSTOM, LOADING, COMPLETE})
@Retention(RetentionPolicy.SOURCE)
public @interface interfaceType {
}
public int page = 1;
public int number = 10;
@Override
public int returnFragmentLayout() {
return R.layout.common_page_list;
}
@Override
public void onRefresh() {
loadFirst();
}
public void loadFirst() {
page = 1;
setUIState(LOADING);
loadData();
}
@Override
public void onLoadNext() {
if (number > 0) {
page++;
loadData();
}
}
public abstract void loadData();
protected void handleData(List data) {
if (data == null || data.size() == 0) {
if (page == 1) {
setUIState(NULL);
adapter.clear();
} else {
setUIState(COMPLETE);
}
} else {
setUIState(NORMAL);
if (number < 0 || data.size() < number) {
adapter.setLoadingState(BasePageListAdapter.COMPLETE);
} else {
adapter.setLoadingState(BasePageListAdapter.LOADMORE);
}
if (page == 1) {
adapter.replaceItems(data);
} else {
adapter.addItems(data);
}
}
}
public void setUIState(@interfaceType int resultState) {
switch (resultState) {
case NULL:
adapter.setLoadingState(BasePageListAdapter.NULL);
break;
case ERROR:
adapter.setLoadingState(BasePageListAdapter.NULL);
break;
case NORMAL:
break;
case LOADING:
adapter.setLoadingState(BasePageListAdapter.LOADING);
break;
case COMPLETE:
default:
adapter.setLoadingState(BasePageListAdapter.COMPLETE);
break;
}
setRefreshing(false);
}
...
}
public class RecyclerLoadNextListener extends RecyclerView.OnScrollListener {
...
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
try {
BasePageListAdapter adapter = (BasePageListAdapter) recyclerView.getAdapter();
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int last = layoutManager.findLastVisibleItemPosition();
int total = layoutManager.getItemCount();
if (total - 2 <= last && mNextListener != null && adapter.isReadyToLoadNext()) {
// 加载下一页
adapter.setReadyToLoadNext(false);
mNextListener.onLoadNext();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
自定义CircleImageView,源码解析
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;
private boolean mDisableCircularTransformation;
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() {
// 默认为 ScaleType.CENTER_CROP 且不可修改
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 (mDisableCircularTransformation) {
super.onDraw(canvas);
return;
}
if (mBitmap == null) {
return;
}
if (mFillColor != Color.TRANSPARENT) {
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
}
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
if (mBorderWidth > 0) {
canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setup();
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
setup();
}
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
super.setPaddingRelative(start, top, end, bottom);
setup();
}
public int getBorderColor() {
return mBorderColor;
}
public void setBorderColor(@ColorInt int borderColor) {
if (borderColor == mBorderColor) {
return;
}
mBorderColor = borderColor;
mBorderPaint.setColor(mBorderColor);
invalidate();
}
/**
* @deprecated Use {@link #setBorderColor(int)} instead
*/
@Deprecated
public void setBorderColorResource(@ColorRes int borderColorRes) {
setBorderColor(getContext().getResources().getColor(borderColorRes));
}
/**
* Return the color drawn behind the circle-shaped drawable.
*
* @return The color drawn behind the drawable
*
* @deprecated Fill color support is going to be removed in the future
*/
@Deprecated
public int getFillColor() {
return mFillColor;
}
/**
* Set a color to be drawn behind the circle-shaped drawable. Note that
* this has no effect if the drawable is opaque or no drawable is set.
*
* @param fillColor The color to be drawn behind the drawable
*
* @deprecated Fill color support is going to be removed in the future
*/
@Deprecated
public void setFillColor(@ColorInt int fillColor) {
if (fillColor == mFillColor) {
return;
}
mFillColor = fillColor;
mFillPaint.setColor(fillColor);
invalidate();
}
/**
* Set a color to be drawn behind the circle-shaped drawable. Note that
* this has no effect if the drawable is opaque or no drawable is set.
*
* @param fillColorRes The color resource to be resolved to a color and
* drawn behind the drawable
*
* @deprecated Fill color support is going to be removed in the future
*/
@Deprecated
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();
}
public boolean isDisableCircularTransformation() {
return mDisableCircularTransformation;
}
public void setDisableCircularTransformation(boolean disableCircularTransformation) {
if (mDisableCircularTransformation == disableCircularTransformation) {
return;
}
mDisableCircularTransformation = disableCircularTransformation;
initializeBitmap();
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
initializeBitmap();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
initializeBitmap();
}
@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
initializeBitmap();
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
initializeBitmap();
}
@Override
public void setColorFilter(ColorFilter cf) {
if (cf == mColorFilter) {
return;
}
mColorFilter = cf;
applyColorFilter();
invalidate();
}
@Override
public ColorFilter getColorFilter() {
return mColorFilter;
}
private void applyColorFilter() {
if (mBitmapPaint != null) {
mBitmapPaint.setColorFilter(mColorFilter);
}
}
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 initializeBitmap() {
if (mDisableCircularTransformation) {
mBitmap = null;
} else {
mBitmap = getBitmapFromDrawable(getDrawable());
}
setup();
}
private void setup() {
if (!mReady) {
mSetupPending = true;
return;
}
if (getWidth() == 0 && getHeight() == 0) {
return;
}
if (mBitmap == null) {
invalidate();
return;
}
// Shader used to draw a bitmap as a texture. The bitmap can be repeated or mirrored by setting the tiling mode.
// BitmapShader的作用是使用特定的图片来作为纹理来使用
// CLMP 如果需要填充的内容大小超过了bitmap size 就选bitmap 边界的颜色进行扩展
// REPEA T重复,不断的重复bitmap去填满,如果绘制的区域大于纹理图片的话,纹理图片会在这片区域不断重复
// MIRROR 镜像的去填满。如果绘制的区域大于纹理图片的话,纹理图片会以镜像的形式重复出现
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(calculateBounds());
// 为什么是这样算的?
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
mDrawableRect.set(mBorderRect);
if (!mBorderOverlay && mBorderWidth > 0) {
// inset 将一个Drawable嵌入
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
}
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
applyColorFilter();
updateShaderMatrix();
invalidate();
}
// 计算Border绘制区域
private RectF calculateBounds() {
int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
int sideLength = Math.min(availableWidth, availableHeight);
float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
return new RectF(left, top, left + sideLength, top + sideLength);
}
// 控制变换
/**
Matrix是Android提供的一个矩阵工具类,本身不能对图像或组件进行变换,但它可以和其它API结合起来控制图形、组件的变换;
Matrix提供了如下方法来控制平移、旋转和缩放:
setTranslate(float dx,float dy):控制Matrix进行平移;
setSkew(float kx,float ky,float px,float py):控制Matrix以px,py为轴心进行倾斜,kx,ky为X,Y方向上的倾斜距离;
setRotate(float degress):控制Matrix进行旋转,degress控制旋转的角度;
setRorate(float degress,float px,float py):设置以px,py为轴心进行旋转,degress控制旋转角度;
setScale(float sx,float sy):设置Matrix进行缩放,sx,sy控制X,Y方向上的缩放比例;
setScale(float sx,float sy,float px,float py):设置Matrix以px,py为轴心进行缩放,sx,sy控制X,Y方向上的缩放比例;
Andorid的API提供了set、post和pre三种操作:
set是直接设置Matrix的值,每次set一次,整个Matrix的数组都会变掉;
post是后乘,当前的矩阵乘以参数给出的矩阵。可以连续多次使用post,来完成所需的整个变换。
pre是前乘,参数给出的矩阵乘以当前的矩阵。所以操作是在当前矩阵的最前面发生的。
*/
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);
}
}
图片加载框架
Picasso使用
// 简单使用
Picasso.with(this).load(url).into(imageView);
// 同步
try {
Bitmap bitmap = Picasso.with(this).load(URL).get();
} catch (IOException e) {
e.printStackTrace();
}
// 异步
Picasso.with(this).load(URL).fetch(new Callback() {
@Override
public void onSuccess() {
//加载成功
}
@Override
public void onError() {
//加载失败
}
});
Target target = new Target() {
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
if (progressBar != null) progressBar.setVisibility(View.GONE);
if (imageView.getTag() == this) {
imageView.setImageBitmap(bitmap);
Palette.Builder bulider = Palette.from(bitmap);
Palette palette = bulider.generate();
dominantColor[0] = palette.getDominantColor(Color.WHITE);
}
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
if (progressBar != null) progressBar.setVisibility(View.GONE);
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
imageView.setTag(target);
// 缓存策略
.memoryPolicy(MemoryPolicy.NO_CACHE,MemoryPolicy.NO_STORE)