Android plug-in development guide -- Practice imitation cool dog music home page (custom ImageView control)

1. Preface

The proposed implementation effect part is the song list part in the figure below, that is, the part framed by the red line in the figure. In order to facilitate the implementation of RecyclerView here, for the square picture display control required in the figure, we will consider using a custom ImageView.

2. Basic environment - realize the grid layout of RecyclerView

First, define RecyclerView in the xml file:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/fx_music_song_list_below_time"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="8dp"
    android:paddingRight="8dp"
    />

Then define the layout file of the layout display style of each item. Here I define it as fx_music_song_list_below_time_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <!-- custom ImageView-->
        <com.weizu.mymusicdemo.customcomponents.RoundEqualWidthImageView
            android:id="@+id/fx_music_song_list_below_time_item_img"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/fx_music_rmdt_play"
            android:background="@drawable/fx_music_round_bg"
            android:scaleType="centerCrop"
            app:radius="8dp"
            />
            
        <LinearLayout
            android:layout_width="200dp"
            android:layout_height="25dp"
            android:gravity="center"
            android:orientation="horizontal"
            android:layout_alignBottom="@id/fx_music_song_list_below_time_item_img"
            >
            <ImageView
                android:layout_width="0dp"
                android:layout_height="20dp"
                android:layout_weight="1"
                android:src="@drawable/fx_music_song_list_below_time_item_play"
                />
            <TextView
                android:id="@+id/fx_music_song_list_below_time_item_play_number"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="2"
                android:text="123 ten thousand"
                android:textColor="@color/white"
                android:gravity="left|center_vertical"
                android:textSize="12sp"
                />
        </LinearLayout>
    </RelativeLayout>

    <TextView
        android:id="@+id/fx_music_song_list_below_time_item_intro"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="123 ten thousand"
        android:textSize="12sp"
        android:textColor="@color/black"
        />

</LinearLayout>

Finally, define an adapter to load data into specific item items:

public class FxPageMusicSongListBelowTimeRecycleViewAdapter<T extends FxPageSongListItemBean> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private Context mContext;
    private int mRescourseId;
    private List<T> mData;

    public FxPageMusicSongListBelowTimeRecycleViewAdapter(Context context, int rescourseId, List<T> data) {
        this.mContext = context;
        this.mRescourseId = rescourseId;
        this.mData = data;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // Instantiate the corresponding sub item xml file
        View rootView = LayoutInflater.from(mContext).inflate(mRescourseId, parent, false);
        return new MyViewHolder(rootView);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        MyViewHolder myViewHolder = (MyViewHolder) holder;
        // Set data to each child
        FxPageSongListItemBean bean = (FxPageSongListItemBean) mData.get(position);
        Glide.with(mContext).load(bean.getCoverImageUrl()).into(myViewHolder.coverImageView);
        myViewHolder.playNumber.setText(bean.getPlayNumber());
        myViewHolder.introduce.setText(bean.getIntroduceInfo());
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

        private ImageView coverImageView;
        private TextView playNumber;
        private TextView introduce;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            coverImageView = itemView.findViewById(R.id.fx_music_song_list_below_time_item_img);
            playNumber = itemView.findViewById(R.id.fx_music_song_list_below_time_item_play_number);
            introduce = itemView.findViewById(R.id.fx_music_song_list_below_time_item_intro);
        }
    }
}

Of course, the last step is to use, that is, instantiate the adapter object to get the RecyclerView instance. Then set up the grid layout manager and simply set the spacing:

FxPageMusicSongListBelowTimeRecycleViewAdapter<FxPageSongListItemBean> adapter =
        new FxPageMusicSongListBelowTimeRecycleViewAdapter<>(getContext(), R.layout.fx_music_song_list_below_time_item, beans);

GridLayoutManager manager = new GridLayoutManager(getContext(), 3);
recyclerView.setLayoutManager(manager);
// Set Item spacing
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.left = 8;
        outRect.right = 8;
        outRect.top = 16;
        outRect.bottom = 16;
    }
});
recyclerView.setAdapter(adapter);

effect:

But it's easy to find that the number of people displayed at the bottom of each picture can't be seen here. In fact, looking at the original picture, you can find that cool dog music actually uses a dark background at the bottom. So here I will add a dark background gradient when customizing the ImageView. Then, let's move on to the topic of this blog, that is, customizing the ImageView control.

3. Customize ImageView

In fact, you can see from the previous xml layout file that the custom ImageView here is the com.weizu.mymusicdemo.customcomponents.RoundEqualWidthImageView class, and the fillet radius is the custom attribute.

  • Fillet can be realized by cutting tools of canvas;
  • The dark gradient of the mask layer can be used to draw another picture on it;
  • When measuring, set the same width to get a square;
  • Set the zoom type to make it cut in the center and fill up;

Then it can be defined as:

public class RoundEqualWidthImageView extends AppCompatImageView {
    private int width;
    private int height;
    private int roundDp;

    public RoundEqualWidthImageView(Context context) {
        super(context);
    }

    public RoundEqualWidthImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initData(context, attrs);
    }

    private void initData(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.roundEqualWidthImageView);
        // The default is right angle, without radian
        roundDp = array.getDimensionPixelOffset(R.styleable.roundEqualWidthImageView_radius, 0);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        width = getWidth();
        height = getHeight();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);  // Set the same width
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // Set zoom type
        setScaleType(ScaleType.CENTER_CROP);
        if (width < roundDp || height < roundDp) roundDp = 5;
        Path path = new Path();
        // Four fillet clipping
        path.moveTo(roundDp, 0);
        path.lineTo(width - roundDp, 0);
        path.quadTo(width, 0, width, roundDp);
        path.lineTo(width, height - roundDp);
        path.quadTo(width, height, width - roundDp, height);
        path.lineTo(roundDp, height);
        path.quadTo(0, height, 0, height - roundDp);
        path.lineTo(0, roundDp);
        path.quadTo(0, 0, roundDp, 0);
        canvas.clipPath(path);
        super.onDraw(canvas);
        // Draw a dark gradient
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.fx_music_song_list_below_time_item_cover_bg, null);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
}

For another fillet radius attribute, create a new values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="roundEqualWidthImageView">
        <attr name="radius" format="dimension"/>
    </declare-styleable>

</resources>

When using, simply specify the basic attributes and fillet radius:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <com.weizu.mymusicdemo.customcomponents.RoundEqualWidthImageView
            android:id="@+id/fx_music_song_list_below_time_item_img"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/fx_music_rmdt_play"
            app:radius="8dp"
            />
            ...
    </RelativeLayout>
</LinearLayout>

Effect now:

Similarly, in order to record, I uploaded this part of the code to github: mymusicdemo-03.

3. Postscript

The Glide used when loading pictures suddenly reminds me of the simple package of some single / multi-threaded breakpoint downloads I wrote before. I feel empty. I can study how to go further.

References

Tags: Java Android

Posted on Thu, 11 Nov 2021 03:24:03 -0500 by Zyx