package com.ttstd.dialer.adapter; import android.content.ClipData; import android.content.ComponentName; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.view.DragEvent; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.FragmentActivity; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import com.shehuan.niv.NiceImageView; import com.tencent.mmkv.MMKV; import com.ttstd.dialer.R; import com.ttstd.dialer.config.CommonConfig; import com.ttstd.dialer.db.app.AppInfo; import com.ttstd.dialer.utils.ApkUtils; import com.ttstd.dialer.utils.Logger; import com.ttstd.iconloader.IconCacheManager; import com.ttstd.iconloader.IconLoader; import org.jetbrains.annotations.NotNull; import java.util.List; public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderCallbacks { private static final String TAG = "AppGridAdapter"; private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); private FragmentActivity mContext; private LoaderManager mLoaderManager; private IconCacheManager mIconCacheManager = IconCacheManager.getInstance(); private List mAppInfos; public AppGridAdapter(FragmentActivity context, LoaderManager loaderManager) { mContext = context; mLoaderManager = loaderManager; } public void setAppInfos(List appInfos) { mAppInfos = appInfos; notifyDataSetChanged(); } @Override public int getCount() { return mAppInfos == null ? 0 : mAppInfos.size(); } @Override public Object getItem(int position) { return mAppInfos == null ? null : mAppInfos.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.item_grid_app, parent, false); holder = new ViewHolder(); holder.root = convertView.findViewById(R.id.root); holder.iv_icon = convertView.findViewById(R.id.iv_icon); holder.tv_app_name = convertView.findViewById(R.id.tv_app_name); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } AppInfo appInfo = mAppInfos.get(position); Drawable drawable = mIconCacheManager.getIcon(appInfo.getComponentName().flattenToShortString()); if (drawable != null) { holder.iv_icon.setImageDrawable(drawable); } else { mLoaderManager.restartLoader(position, null, this).forceLoad(); } holder.tv_app_name.setText(appInfo.getLabel()); holder.root.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ApkUtils.openApp(mContext, appInfo.getComponentName()); } }); holder.root.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (!v.isAttachedToWindow()) { Logger.e(TAG, "View is not attached to window"); return false; } // 添加触感反馈 v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); ClipData data = ClipData.newPlainText("appInfo", appInfo.getPackageName()); View.DragShadowBuilder shadowBuilder = new LauncherDragShadowBuilder(v); // 使用 post 确保在当前事件处理完成后再开始拖拽,提高稳定性并减少崩溃 v.post(() -> { boolean started = false; try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { started = v.startDragAndDrop(data, shadowBuilder, appInfo, View.DRAG_FLAG_OPAQUE); } else { started = v.startDrag(data, shadowBuilder, appInfo, 0); } } catch (Exception e) { Logger.e(TAG, "startDragAndDrop error: " + e.getMessage()); } Logger.e(TAG, "startDragAndDrop result: " + started); if (started) { v.setAlpha(0.5f); } }); return true; } }); holder.root.setOnDragListener(new View.OnDragListener() { @Override public boolean onDrag(View v, DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: if (event.getLocalState() == appInfo) { v.setAlpha(0.5f); return true; } return false; case DragEvent.ACTION_DRAG_ENDED: v.setAlpha(1.0f); return true; } return false; } }); return convertView; } private static class LauncherDragShadowBuilder extends View.DragShadowBuilder { public LauncherDragShadowBuilder(View view) { super(view); } @Override public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { float scale = 1.2f; int width = (int) (getView().getWidth() * scale); int height = (int) (getView().getHeight() * scale); outShadowSize.set(width, height); outShadowTouchPoint.set(width / 2, height / 2); } @Override public void onDrawShadow(Canvas canvas) { float scale = 1.2f; canvas.save(); canvas.scale(scale, scale); // 确保在绘制阴影时视图是不透明的 float originalAlpha = getView().getAlpha(); getView().setAlpha(1.0f); getView().draw(canvas); getView().setAlpha(originalAlpha); canvas.restore(); } } @NonNull @NotNull @Override public Loader onCreateLoader(int id, @Nullable @org.jetbrains.annotations.Nullable Bundle args) { AppInfo appInfo = mAppInfos.get(id); ComponentName componentName = appInfo.getComponentName(); return new IconLoader(mContext, componentName, mIconCacheManager); } @Override public void onLoadFinished(@NonNull @NotNull Loader loader, Drawable data) { int position = loader.getId(); notifyDataSetChanged(); } @Override public void onLoaderReset(@NonNull @NotNull Loader loader) { Logger.e(TAG, "onLoaderReset: "); } static class ViewHolder { ConstraintLayout root; NiceImageView iv_icon; TextView tv_app_name; } }